zammad-mcp
MCP server for Zammad that focuses on workflows the standard Zammad API tooling does not cover well — primarily shared drafts with strict reply-HTML validation, fresh signature rendering and German-localised quote blocks.
README
zammad-mcp
MCP server for Zammad that focuses on workflows the standard Zammad API tooling does not cover well — primarily shared drafts with strict reply-HTML validation, fresh signature rendering and German- localised quote blocks.
Built to coexist with generic Zammad MCP servers (e.g.
basher83/zammad-mcp) — this one
deliberately covers only a narrow set of opinionated workflows.
Tools
zammad_create_shared_draft— Reply-All shared draft with strict reply-HTML validation and signature rendering.zammad_get_ticket_thread— Ticket meta + all articles (with bodies) in one round-trip.zammad_add_internal_note— Append an internal note (hard-codedtype=note, internal=true).
zammad_create_shared_draft
Creates or overwrites the shared draft of a Zammad ticket as a Reply-All email.
What the server does automatically:
- Finds the last incoming customer article (
sender=Customer,type=email; falls back to the most recent article if none). - Computes
to,cc,subject,in_reply_toandfromfrom that article plus/users/meand the ticket's group email-address. - Filters configured self-addresses out of CC (so you don't reply to yourself).
- Fetches the signature template fresh from Zammad and resolves all
#{...}placeholders via lazy-loaded sub-objects (with caching). Defensively strips HTML tags that may have crept into placeholders via the Zammad WYSIWYG editor. - Appends the original article as a German-localised
<blockquote>with Europe/Berlin date (CET/CEST aware). - Wraps the signature in
<div data-signature="true" data-signature-id="X">so Zammad does not stack a second signature on top when the draft is opened. PUTs the assembled payload to/tickets/<id>/shared_draft.
What you provide:
ticket_id— Zammad ticket ID (numeric, from the URL/#ticket/zoom/<id>).reply_html— the actual reply body as HTML with a nested<div>structure (see validation below).signature_id(optional, default1) — which signature to render.extra_cc(optional) — additional CC addresses to add on top of the automatic Reply-All set.quote_locale(optional,enorde) — language for the quote block's date format and "wrote:" lead-in. When omitted, the server default (ZAMMAD_QUOTE_LOCALE, falling back toen) is used.
Reply-HTML validation
The tool refuses the call if any of these issues are found in reply_html:
| Code | Rule |
|---|---|
P_TAG |
No top-level <p> tags (content inside <blockquote> is ignored). Use nested <div> instead — Zammad's editor produces doubled empty lines from <p> blocks. |
DOUBLE_BR |
No <br><br> sequences. Use <div><br></div> for paragraph spacing. |
ASCII_QUOTE |
No straight ASCII " in visible text. Use typographically correct quotes for your language. |
WRONG_CLOSING_QUOTE |
If the text uses the German opening quote „ (U+201E), it must close with ” (U+201D), not with “ (U+201C, which is the English opener). |
ASCII_APOSTROPHE |
No ASCII ' inside a word. Use ’ (U+2019). |
WRONG_DASH_LOCALE (locale=de only) |
German body uses em-dash — (U+2014). German typography uses en-dash – (U+2013) with spaces as parenthetical dash. |
ASCII_DASH_AS_GEDANKENSTRICH (locale=de only) |
German body uses - (ASCII hyphen with spaces) as parenthetical dash. Use – (en-dash with spaces) instead. |
SIGNATURE_DUPLICATE (configurable) |
The body contains a name listed in ZAMMAD_BANNED_NAMES. Prevents agents from typing the name that the signature already provides. |
MISSING_GREETING (configurable) |
The body does not contain the string configured in ZAMMAD_REQUIRED_GREETING. |
Universal checks (P_TAG, DOUBLE_BR, ASCII_QUOTE, WRONG_CLOSING_QUOTE,
ASCII_APOSTROPHE) are always on. The two configurable checks are silent
when their respective env-var is empty.
Example reply_html
<div>
<div>Dear Mr Smith,</div>
<div><br></div>
<div>thank you for your message — we have resolved the issue.</div>
<div><br></div>
<div>Best regards</div>
</div>
Response
{
"ok": true,
"ticket_url": "https://zammad.example.com/#ticket/zoom/12345",
"to": "customer@example.com",
"cc": "colleague@example.com",
"from": "Jane Doe <support@example.com>",
"subject": "RE: Question about hosting",
"in_reply_to": "<abc123@example.com>",
"reference_article_id": 98765,
"draft_id": null
}
(draft_id is null whenever Zammad does not return an id in the PUT
response — the draft is still created, only the metadata is absent.)
On validation failure:
{
"ok": false,
"error": "INVALID_REPLY_HTML",
"issues": [
{ "code": "P_TAG", "msg": "Top-level <p>-Tag bei Char 142 gefunden. ..." }
]
}
zammad_get_ticket_thread
Fetches a ticket and all of its articles in a single call. Useful for
"give me context on ticket X before I write anything" — combines two
Zammad endpoints (/tickets/<id>?expand=true and
/ticket_articles/by_ticket/<id>) and returns a flat structure with
ticket meta plus the article list.
Parameters:
ticket_id— numeric ticket ID.include_internal(defaulttrue) — set tofalseto hide internal notes from the result.include_bodies(defaulttrue) — set tofalseto get a cheap meta- only overview of long threads.max_articles(optional) — caps to the most recent N articles.
Response: { ok, ticket_url, ticket: {...}, article_count_returned, article_count_total, truncated, articles: [...] }.
zammad_add_internal_note
Appends an internal note to a ticket. The tool hard-codes
type: "note" and internal: true, so it is structurally impossible
to accidentally send an email to the customer. For customer-facing
content use zammad_create_shared_draft and let a human send the draft
from the Zammad UI.
Parameters:
ticket_id— numeric ticket ID.body— body content (HTML or plain text).content_type—text/html(default) ortext/plain.subject(optional) — internal-list subject.
Response: { ok, ticket_url, article_id, type, internal }.
Setup
git clone <repo-url> zammad-mcp
cd zammad-mcp
npm install
npm run build
npm test
Node 18 or higher.
Configuration
| Env-var | Required | Description |
|---|---|---|
ZAMMAD_URL |
yes | REST base URL, e.g. https://mail.example.com/api/v1/. |
ZAMMAD_HTTP_TOKEN |
yes | API token (Profile → Token Access in Zammad). |
ZAMMAD_SELF_EMAILS |
no | Comma-separated list of own addresses that should never appear in CC. Default: empty (no filtering). |
ZAMMAD_BANNED_NAMES |
no | Comma-separated list of name patterns the reply body must not contain (typically: your own name, because the signature already supplies it). Default: empty. |
ZAMMAD_REQUIRED_GREETING |
no | If set, every reply body must contain this string (case-insensitive). Default: empty. |
ZAMMAD_QUOTE_LOCALE |
no | Default locale for the quote-block lead-in. Either en (default) or de. Per-call overridable via the quote_locale tool parameter. |
See .env.example for a starter file.
Registration with Claude
Add this block to mcpServers in your Claude Desktop config
(~/Library/Application Support/Claude/claude_desktop_config.json on macOS)
and / or your Claude Code config (~/.claude.json):
"zammad-mcp": {
"command": "node",
"args": ["/absolute/path/to/zammad-mcp/dist/index.js"],
"env": {
"ZAMMAD_URL": "https://mail.example.com/api/v1/",
"ZAMMAD_HTTP_TOKEN": "...",
"ZAMMAD_SELF_EMAILS": "support@example.com,me@example.com",
"ZAMMAD_BANNED_NAMES": "Jane Doe,Jane",
"ZAMMAD_REQUIRED_GREETING": "Best regards",
"ZAMMAD_QUOTE_LOCALE": "en"
}
}
Restart Claude Desktop completely (Cmd+Q + re-open) so the daemon reloads the MCP server list. In Claude Code a new chat is enough.
Tests
npm test
Unit tests use Node's built-in test runner via --experimental-strip-types.
The signature resolver is tested with a mock Zammad client; everything
else is pure logic and doesn't need network access.
License
MIT
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。