# Zulip MCP Server — 111 tools via DADL

The Zulip DADL turns Zulip's API into an MCP server that Claude, GPT or any MCP-compatible agent can consume directly. One YAML file declares all 111 tools — user, message, realm, custom, stream, draft, and more — and ToolMesh serves them at runtime. No Python boilerplate, no per-endpoint code, no separate MCP server process.

Below: the endpoint coverage matrix, a two-block ToolMesh setup, the full tool reference grouped by Zulip feature area, required credential scopes.

**Source:** [Zulip REST API](https://zulip.com/api/rest)

**Updated:** 2026-05-28

**Tags:** communication, crud, user-management, notifications, real-time, logging, automation, issue-tracking, auth:basic

## Which Zulip endpoints are covered?

**67%** (111 of ~165 endpoints).

**Focus:** messages (send + get + get-one + update + delete + render + history + reactions + flags + flags-for-narrow + mark-as-read + read-receipts + typing + matches-narrow), channels/streams (list + get-by-id + get-id-by-name + update + archive + topics + subscribers + delete-topic + subscription-status + email-address), subscriptions (list + subscribe-or-create + unsubscribe + update-settings), users (list + get + by-email + own + create + update + deactivate + reactivate + presence + status + regenerate-own-api-key + update-own-settings), user groups (list + create + update + deactivate + members + subgroups), personal preferences (mute/unmute user, topic visibility policy follow/mute/unmute, alert words CRUD), drafts (CRUD), saved snippets (CRUD), scheduled messages (CRUD), real-time events (register + get + delete), invitations (send + list + resend + revoke + multi-use), attachments (list + delete), files (upload + custom emoji upload), org admin (update_realm + user_settings_defaults + linkifiers full CRUD + reorder + custom profile fields full CRUD + reorder + code playgrounds + realm domains + default streams + bot CRUD + regenerate bot api key), server/realm (server settings + linkifiers list + custom emoji list + deactivate)

**Missing:** OAuth/SAML/external auth flows, mobile push device registration (APNS/FCM tokens), email confirmation flows, password reset flows, video call provider connection setup (the realm setting is covered, the per-call plumbing isn't), navigation views, message reminders (3 endpoints), realm export/import requests, SCIM provisioning, in-app hotspots & tutorial step tracking, BotConfig per-bot key-value storage (3 endpoints), some dev-only endpoints, billing/plan management

*Last reviewed: 2026-05-28*

## How do you configure the Zulip DADL?

1. Decide whether you want to use a human user account or a dedicated bot (recommended for production).
2. For a bot: log into Zulip → click your avatar → Personal Settings → Bots → Add a new bot.
3. Choose type 'Generic bot' (most flexible). Give it a name and email. Click Create.
4. Click the download icon next to the new bot to view its API key, or click the copy icon.
5. For a human account: Personal Settings → Account & privacy → API key → Manage your API key → enter your password → copy the key.
6. Store BOTH the email address and the API key — they are used together as HTTP Basic credentials.
7. Subscribe the bot to any channels it needs to read or post in (channels → settings → Add subscribers).
8. Note your organization URL: https://YOUR-ORG.zulipchat.com (Zulip Cloud) or your self-hosted domain.
9. Set BOTH env vars in your ToolMesh deployment:
10.   CREDENTIAL_ZULIP_EMAIL=<bot or user email address>  (used as HTTP Basic username)
11.   CREDENTIAL_ZULIP_API_KEY=<api key>                  (used as HTTP Basic password)

**Environment variable:** `CREDENTIAL_ZULIP_API_KEY`

[Authentication docs](https://zulip.com/api/api-keys)

*Two credentials are needed: zulip_email (the bot's or user's email) and zulip_api_key (the API key string). Both are sent as HTTP Basic auth — ToolMesh handles the base64 encoding. The url in backends.yaml is the bare org URL WITHOUT the /api/v1 suffix (the DADL adds it). For Zulip Cloud orgs the URL pattern is https://<slug>.zulipchat.com; for self-hosted it is whatever the admin configured. Anyone with the API key can impersonate the bot — treat it as a secret.*

## How do you install the Zulip MCP server with ToolMesh?

Add to your `backends.yaml`:

```yaml
- name: zulip
  transport: rest
  dadl: zulip.dadl
  url: "https://your-org.zulipchat.com"

```

Set the credential:

```
CREDENTIAL_ZULIP_API_KEY=your-token-here
```

## What 111 tools does the Zulip DADL expose?

- **POST** `send_message` — Send a message to a channel topic or as a direct message. For channel messages set type=channel (or 'stream'), to=<channel name or stream_id>, topic=<topic name>, content=<markdown>. For DMs set type=direct (or 'private'), to=<user email/id or JSON array of user IDs>, content=<markdown>. Returns {id, automatic_new_visibility_policy?}.

- **GET** `get_messages` — Fetch a range of messages around an anchor. Use anchor + num_before + num_after to page; anchor can be a message ID or one of: 'newest', 'oldest', 'first_unread'. narrow is a JSON-encoded array of filter objects, e.g. '[{"operator":"channel","operand":"general"},{"operator":"topic","operand":"deploys"}]'. Operators: channel, topic, sender, search, is, has, dm, dm-including, id, with. Max 5000 messages per request (recommend ≤1000). Response includes {messages, anchor, found_oldest, found_newest, found_anchor, history_limited}.

- **GET** `get_message` — Fetch a single message by ID. Returns the message object with content, sender info, topic, channel, reactions, flags, and timestamps.
- **PATCH** `update_message` — Edit a message's content, topic, or move it to a different channel. propagate_mode controls scope: change_one (default, this message), change_later (this + later in same topic), change_all (entire topic). Only the sender or users with appropriate permissions can edit.

- **DELETE** `delete_message` — Permanently delete a message. Only the sender (within edit window) or organization administrators can delete. Cannot be undone.
- **POST** `render_message` — Render a Markdown string to HTML using Zulip-flavored Markdown, without sending it. Useful for previewing how a message will look.
- **GET** `get_message_history` — Get the edit history of a message. Returns array of snapshots (oldest first) including topic, content, rendered_content, timestamp, user_id, and prev_* fields where changed.
- **POST** `add_reaction` — Add an emoji reaction to a message. emoji_name is the Zulip emoji name (e.g. 'thumbs_up', 'octopus').
- **DELETE** `remove_reaction` — Remove your own emoji reaction from a message.
- **POST** `update_message_flags` — Add or remove a flag on a batch of messages by ID. Flags: read, starred, collapsed. Some flags are read-only and change automatically (mentioned, has_alert_word, etc.). op is 'add' or 'remove'.

- **POST** `update_message_flags_for_narrow` — Add or remove a flag across all messages matching a narrow. Useful for 'mark all unread in this channel as read'. anchor + num_before + num_after define the scope (same semantics as get_messages). Returns processed_count, updated_count, first/last_processed_id, found_oldest/found_newest.

- **POST** `mark_all_as_read` — Mark every unread message visible to the current user as read. Deprecated — prefer update_message_flags_for_narrow with anchor=oldest, num_after=large.
- **POST** `mark_stream_as_read` — Mark all unread messages in a channel as read. Deprecated — prefer update_message_flags_for_narrow with a channel narrow.
- **POST** `mark_topic_as_read` — Mark all unread messages in a topic as read. Deprecated — prefer update_message_flags_for_narrow with channel + topic narrow.
- **GET** `get_read_receipts` — Get the list of user IDs who have read a message. Excludes the sender, users with send_read_receipts disabled, and muted users.
- **POST** `set_typing_status` — Send a "user is typing" notification. Clients call this every few seconds while typing, then send op=stop when done. type=direct needs `to` (JSON array of user IDs); type=channel needs stream_id + topic.

- **GET** `check_messages_match_narrow` — Test which of a given set of message IDs match a narrow. Useful for selectively highlighting search hits in a list. Returns a dict keyed by message ID with match_content + match_subject (HTML, with search keyword highlighting). Messages that don't match are omitted.

- **GET** `get_streams` — List channels visible to the authenticated user. Filters control which categories are included (public, subscribed, default, archived). Returns array of stream objects with stream_id, name, description, invite_only, is_web_public, history_public_to_subscribers, message_retention_days, and permission groups.

- **GET** `get_stream` — Get full details of a channel by its numeric ID. Returns stream object with name, description, privacy, retention, permission groups, subscriber_count, stream_weekly_traffic.
- **GET** `get_stream_id` — Look up a channel's numeric ID by its exact name. Returns {stream_id}. Returns code=BAD_REQUEST if the channel does not exist or is not accessible.
- **PATCH** `update_stream` — Update a channel's settings. Requires channel admin permission. All body params optional — only fields you pass are changed. Permission group settings take a JSON-encoded object like {"new":"role:everyone"} or a group ID.

- **DELETE** `archive_stream` — Archive (delete) a channel. Messages remain in the database but the channel is hidden. Organization admins can unarchive via update_stream. Irreversible from the API side without admin recovery.
- **GET** `get_stream_topics` — List topics in a channel, ordered by recency (most recent first). Returns array of {name, max_id (last message ID)}. The user must have access to the channel.
- **GET** `get_subscribers` — List user IDs subscribed to a channel. Returns array of integers.
- **POST** `delete_topic` — Delete an entire topic and all its messages. Admin-only. Processes in batches — the response contains 'complete' (boolean); if false, RETRY the same call until complete=true.

- **GET** `get_subscription_status` — Check whether a specific user is subscribed to a specific channel. Returns {is_subscribed}.
- **GET** `get_stream_email_address` — Get the email address that, when emailed, posts to a channel. Used for email integrations.
- **GET** `get_subscriptions` — List channels the current user/bot is subscribed to, with all subscription-specific settings (notifications, color, pin, mute). include_subscribers='partial' returns subscribers only for bots and recently-active users (faster for big orgs).

- **POST** `create_or_subscribe` — Subscribe one or more users to one or more channels. CREATES channels that don't yet exist (this is the only way to create a channel via the API). principals is a JSON array of user IDs or emails — omit to subscribe yourself.

- **DELETE** `unsubscribe` — Unsubscribe one or more users from channels. principals defaults to the current user. Cannot remove admins from public channels they administer.
- **POST** `update_subscription_settings` — Update per-channel subscription preferences (color, pin_to_top, audible_notifications, desktop_notifications, push_notifications, email_notifications, is_muted, in_home_view). subscription_data is a JSON array of {stream_id, property, value} objects.

- **GET** `get_users` — List all users in the organization. Returns array of user objects with id, email, full_name, is_bot, is_active, role, avatar_url, timezone, date_joined.
- **GET** `get_user` — Get one user by numeric ID. Preferred over get_user_by_email because email can change.
- **GET** `get_user_by_email` — Get one user by email address. Email can be the real address or the dummy user{id}@{host} form.
- **GET** `get_own_user` — Get the currently authenticated user (or bot). Returns identity, role, custom profile data, and account flags. The simplest test that auth is configured correctly.
- **POST** `create_user` — Create a new user. Admin-only. The new user receives an email to set their password unless full_name and password are pre-set.
- **PATCH** `update_user` — Update a user's profile or role. Admins can change role and custom fields; users can update their own profile via this endpoint too (some fields).
- **DELETE** `deactivate_user` — Deactivate a user. They can no longer log in or use the API. Their messages remain. Reversible via reactivate_user.
- **POST** `reactivate_user` — Reactivate a previously deactivated user. Admin only.
- **GET** `get_user_presence` — Get a single user's current presence (active/idle, last update timestamp). Path accepts either user ID or email.
- **GET** `get_realm_presence` — Get presence for all users the current user can access. Returns server_timestamp and presences keyed by email.
- **POST** `update_presence` — Update the current user's presence (active/idle/offline). Most clients call this every minute while open.
- **GET** `get_user_status` — Get a user's custom status (status_text, emoji, away flag).
- **POST** `update_status` — Set your own custom status — a short text + optional emoji. Pass empty status_text to clear.
- **POST** `regenerate_own_api_key` — Generate a new API key for the authenticated user. Returns {api_key}. WARNING: the old key stops working immediately and all devices using it (including this connection) must be re-authenticated. Use sparingly.

- **PATCH** `update_own_settings` — Update the authenticated user's own preferences (display, notifications, behavior, privacy). Pass only the fields you want to change. Admins can also use target_users to bulk-update settings for others (Zulip 12.0+).

- **GET** `get_user_groups` — List all user groups in the organization. Returns array of group objects with id, name, description, members (user IDs), direct_subgroup_ids, deactivated.
- **POST** `create_user_group` — Create a new user group with an initial set of members. can_*_group fields accept a JSON-encoded group ID or 'role:everyone'/'role:members' etc.
- **PATCH** `update_user_group` — Update a user group's name, description, or permission settings. Admin-only or has_manage permission.
- **DELETE** `deactivate_user_group` — Deactivate (soft-delete) a user group. The group remains in the database but is hidden. Cannot deactivate system groups.
- **POST** `update_user_group_members` — Add or remove members from a user group. Pass JSON arrays of user IDs for 'add' and 'delete'.
- **POST** `update_user_group_subgroups` — Add or remove nested subgroups from a user group. Pass JSON arrays of group IDs.
- **GET** `get_user_group_members` — List all member user IDs of a group. Set direct_member_only=true to exclude transitively-included members from subgroups.
- **POST** `mute_user` — Mute another user from the current user's perspective. Messages sent by muted users are automatically marked as read and hidden in the UI. Mute state is per-user — only affects the calling account. There is no dedicated "list muted users" endpoint; the current list is delivered via the muted_users key in register_queue fetch.

- **DELETE** `unmute_user` — Unmute a previously muted user. Returns an error if the user is not currently muted.
- **POST** `update_user_topic_visibility` — Set the current user's visibility policy for a specific channel + topic. This is the modern replacement for the deprecated /users/me/subscriptions/muted_topics endpoint. Supports both muting (hide notifications) and following (boost notifications), plus unmute and reset.

- **GET** `get_alert_words` — Get the current user's configured alert words. Alert words trigger a notification when they appear in any message visible to the user. Case-insensitive, max 100 chars per word.

- **POST** `add_alert_words` — Add one or more alert words. alert_words is a JSON-encoded array of strings, e.g. '["deploy","incident"]'.
- **DELETE** `remove_alert_words` — Remove one or more alert words. alert_words is a JSON-encoded array of strings to remove. Returns the remaining alert words.
- **GET** `get_drafts` — Get all drafts saved by the current user (ordered by most recently edited).
- **POST** `create_drafts` — Create one or more drafts. drafts is a JSON array of draft objects: {type, to, topic, content, timestamp}.
- **PATCH** `edit_draft` — Replace an existing draft's contents.
- **DELETE** `delete_draft` — Delete a draft permanently.
- **GET** `get_saved_snippets` — List the current user's saved snippets — reusable text fragments (e.g. canned responses, command incantations, KB-style notes). Returns array of {id, title, content, date_created}.
- **POST** `create_saved_snippet` — Create a saved snippet. Returns {saved_snippet_id}.
- **PATCH** `edit_saved_snippet` — Edit a saved snippet's title or content.
- **DELETE** `delete_saved_snippet` — Permanently delete a saved snippet.
- **GET** `get_scheduled_messages` — List the current user's pending scheduled messages, ordered by delivery time.
- **POST** `create_scheduled_message` — Schedule a message for later delivery. scheduled_delivery_timestamp is a UNIX timestamp in UTC seconds.
- **PATCH** `update_scheduled_message` — Update a pending scheduled message. Can change content, topic, recipients, or scheduled time. Pass only the fields you want to change.
- **DELETE** `delete_scheduled_message` — Cancel a pending scheduled message before it is delivered.
- **POST** `register_queue` — Register an event queue and fetch initial state. The server returns queue_id, last_event_id, and snapshots of requested data. Use get_events to long-poll for new events. Always call delete_queue when finished — queues consume server resources. event_types and fetch_event_types are JSON-encoded arrays of strings.

- **GET** `get_events` — Long-poll for new events on a registered queue. queue_id from register_queue. last_event_id is the highest event ID already processed (-1 for first call). dont_block=true returns immediately (possibly with empty events). Default behavior blocks up to ~10 minutes.

- **DELETE** `delete_queue` — Delete a previously registered event queue. Always call this when done with register_queue to free server resources.
- **POST** `send_invites` — Send email invitations to join the organization. invite_as: 100=owner, 200=admin, 300=moderator, 400=member (default), 600=guest. stream_ids is a JSON array of channels to auto-subscribe. Requires admin role typically.

- **POST** `create_multi_use_invite_link` — Create a reusable invite link that anyone can use to join the org. Returns {invite_link}.
- **GET** `list_invites` — List pending email and multi-use invitations.
- **POST** `resend_invite` — Resend the invitation email for a pending email invitation.
- **DELETE** `revoke_invite` — Revoke a pending email invitation.
- **DELETE** `revoke_multi_use_invite` — Revoke a multi-use invite link.
- **POST** `upload_file` — Upload a file. Pass file as a file_url — ToolMesh will fetch and forward it as multipart. Response includes a relative url like /user_uploads/1/4e/.../filename. To share, embed in a message as [name](url). For files >25MB use the tus resumable upload endpoint (not modeled).

- **GET** `get_attachments` — List the current user's uploaded attachments. Returns {attachments: [...], upload_space_used}.
- **DELETE** `delete_attachment` — Delete an uploaded attachment by ID. The file is removed from storage; messages referencing it will show a broken link.
- **GET** `get_server_settings` — Get public server configuration including zulip_version, zulip_feature_level, available auth methods, and realm branding. Works WITHOUT authentication — useful for discovery.
- **GET** `get_realm_linkifiers` — List linkifiers (regex → URL template rules) configured for the realm. Clients should process them in order. Returns array of {pattern, url_template, id, example_input, ...}.
- **GET** `get_custom_emoji` — Get all custom emoji defined in the realm. Returns map of emoji_id → {id, name, source_url, still_url, deactivated, author_id}.
- **DELETE** `deactivate_custom_emoji` — Deactivate (hide) a realm custom emoji. The image data remains but the emoji is no longer selectable.
- **POST** `upload_custom_emoji` — Upload a new custom emoji to the realm. emoji_name allows letters/digits/dashes/spaces. File goes in the request body as form data. Server limits file size (default 5 MB) and accepts common image/gif formats.

- **PATCH** `update_realm` — Update org-wide (realm) settings. Admin/owner only. Only fields you pass are changed. Setting target_users on the related update_own_settings is for per-user defaults; this endpoint changes org-wide policy. The full realm settings surface is ~80 fields; the most commonly-tuned ones are exposed here. For uncommon fields, pass via this same endpoint.

- **PATCH** `update_realm_user_settings_defaults` — Update the default per-user settings applied to newly-created accounts in the realm. Does NOT change settings of existing users. Admin/owner only.

- **POST** `add_linkifier` — Add a new linkifier (regex → URL template). Used to auto-link ticket IDs like 'JIRA-1234', commit hashes, internal references, etc. url_template uses RFC 6570 with named variables captured from the regex (e.g. '{ticket_id}').

- **PATCH** `update_linkifier` — Update an existing linkifier's pattern, template, or auxiliary fields. Pass only fields to change.
- **DELETE** `remove_linkifier` — Delete a linkifier. Existing messages keep their rendered links but new messages won't auto-link this pattern.
- **PATCH** `reorder_linkifiers` — Reorder linkifiers. Order matters when patterns overlap — earlier wins. Pass a JSON array of all linkifier IDs in the desired order.

- **GET** `get_custom_profile_fields` — List custom profile fields configured for the realm. Returns array of {id, type, name, hint, field_data, order, ...}. Field types: 1=short text, 2=paragraph, 3=dropdown, 4=date, 5=link, 6=users, 7=external account, 8=pronouns.

- **POST** `create_custom_profile_field` — Create a new custom profile field that users can fill in their profile. field_data is type-specific JSON (e.g. choices for dropdown).
- **PATCH** `update_custom_profile_field` — Update a custom profile field. Cannot change field_type after creation.
- **DELETE** `delete_custom_profile_field` — Delete a custom profile field. All user values for this field are lost. Cannot be undone.
- **PATCH** `reorder_custom_profile_fields` — Reorder custom profile fields. Pass a JSON array of field IDs in the desired display order.
- **POST** `add_realm_playground` — Add a code playground integration — clicking the "View in playground" button on a code block of the matching language opens the code in an external editor (e.g. play.rust-lang.org). url_template must contain exactly one `{code}` variable.

- **DELETE** `remove_realm_playground` — Remove a code playground integration.
- **POST** `add_realm_domain` — Add an email domain to the org's allowed-signup list (only effective when realm setting emails_restricted_to_domains=true). Use to lock self-signup to your company domain.

- **PATCH** `update_realm_domain` — Toggle allow_subdomains on an existing realm domain entry.
- **DELETE** `remove_realm_domain` — Remove an email domain from the allowed-signup list.
- **POST** `add_default_stream` — Mark a channel as a default channel — new users are auto-subscribed on signup (unless include_realm_default_subscriptions is overridden in the invite).

- **DELETE** `remove_default_stream` — Remove a channel from the realm's default channel list. Existing subscribers are not affected.
- **GET** `get_bots` — List bots owned by the current user. Returns array of {user_id, username, full_name, api_key, default_sending_stream, default_events_register_stream, default_all_public_streams, avatar_url, services}. IMPORTANT: this endpoint and the other /bots endpoints are for HUMAN accounts only — bot accounts cannot list/manage other bots (Zulip returns 'This endpoint does not accept bot requests'). Connect with a human user's API key to manage bots.

- **POST** `create_bot` — Create a new bot owned by the current user. bot_type: 1=Generic, 2=Incoming webhook, 3=Outgoing webhook, 4=Embedded. Generic is the most flexible.

- **PATCH** `update_bot` — Update settings of a bot owned by the current user (or any bot, for admins).
- **POST** `regenerate_bot_api_key` — Generate a new API key for one of your bots. Old key stops working immediately.
- **DELETE** `deactivate_bot` — Deactivate (delete) a bot owned by the current user. Reversible by re-activating the bot user.

## What composite workflows does the Zulip DADL provide?

- **FN** `send_channel_message` — Convenience wrapper around send_message for the common case of posting to a channel topic. Pass channel_name OR stream_id (one is required) plus topic and content.

- **FN** `search_messages` — Search for messages by free-text query, optionally within a channel and/or topic. Returns up to `limit` matching messages, newest first. Wraps get_messages with a constructed narrow.

- **FN** `get_recent_in_topic` — Get the N most recent messages in a specific channel + topic. Convenience wrapper around get_messages with a channel+topic narrow anchored at 'newest'.

- **FN** `mark_all_in_channel_read` — Mark every unread message in a channel as read (preferred replacement for mark_stream_as_read).
- **FN** `search_users` — Find users by a prefix match on full_name or email (Zulip has no dedicated user-search endpoint, so this fetches the org user list and filters client-side). Bots and inactive users are excluded by default.

- **FN** `daily_summary` — Build a digest of unread message activity for the authenticated user — total unread count, breakdown by channel, top topics, and DM senders. Uses get_messages with an is:unread narrow; for very busy accounts, raise `max_scan` (Zulip caps at 5000 per request).


## Which DADLs are related to Zulip?

- [Zammad](https://www.dadl.ai/d/zammad/) — Zammad helpdesk REST API -- tickets, articles, users, organizations, groups, roles, knowledge base, SLAs, calendars, object manager (custom fields), macros, triggers, overviews, reports, time accounting, and full admin surface. Supports X-On-Behalf-Of impersonation on every endpoint.
- [Graylog](https://www.dadl.ai/d/graylog/) — Graylog REST API -- log search (Views/Search + legacy universal), streams, pipelines, inputs, alerts, events, dashboards, users, roles, sidecars, index management, and cluster administration. Targets Graylog 6.x.
- [Hacker News](https://www.dadl.ai/d/hackernews/) — Hacker News API — read-only access to stories, comments, polls, jobs, users, and live feeds from news.ycombinator.com
- [Mempool](https://www.dadl.ai/d/mempool/) — mempool.space — Bitcoin block explorer, mempool visualizer, fee estimator, Lightning Network explorer, and transaction accelerator
- [Alertmanager](https://www.dadl.ai/d/alertmanager/) — Prometheus Alertmanager API v2 -- alerts, silences, receivers, alert groups, status, and operational health
- [BookStack](https://www.dadl.ai/d/bookstack/) — BookStack wiki and documentation platform REST API -- shelves, books, chapters, pages (HTML/Markdown content), attachments, image gallery, comments, cross-entity search, exports (HTML/Markdown/PDF/ZIP), ZIP imports, users, roles, content permissions, recycle bin, audit log and tags. Hierarchy: shelves > books > chapters > pages.

---

**Canonical URL:** https://www.dadl.ai/d/zulip/
**Raw DADL:** https://github.com/DunkelCloud/dadl-registry/blob/main/zulip.dadl
