Zammad MCP Server
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Zammad MCP Servershow me my open Zammad tickets"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Zammad MCP Server
A Model Context Protocol server for the Zammad helpdesk. It lets Claude, or any MCP client, work with Zammad tickets using the caller's own Zammad permissions: every request carries a per-user API token, never a shared admin token, so Zammad itself decides what each user can see and change.
Status: Ticket, article, attachment, search, user, organization, tag and knowledge base tools work in stdio and HTTP, and email replies and macros are available behind opt-in flags. Platform mode (SSO with per-user minted tokens) is available.
Modes
Community mode (available now)
Works against any Zammad 6.x or 7.x with a personal API token (Zammad: avatar menu, Profile, Token Access).
stdio for a local MCP client:
zammad-mcp stdio, with the token inZAMMAD_HTTP_TOKEN.Streamable HTTP:
zammad-mcp http, withGET /healthzfor health checks./http/api-key/mcp(always on): every caller sends their own personal token asX-Zammad-Token. Requests on this path never useZAMMAD_HTTP_TOKEN, and anyX-Auth-Request-*header (also spelled with_) is stripped on arrival./mcp(opt-in): servesZAMMAD_HTTP_TOKENto anyone who can reach the port, who then acts in Zammad as that token's owner. It is only mounted withZAMMAD_HTTP_SHARED_TOKEN_ROUTE=true;zammad-mcp httprefuses to start withZAMMAD_HTTP_TOKENset and the flag off. Use it only on a loopback or otherwise private listener.
Platform mode
For deployments that put Zammad behind an SSO edge with a trusted-header middleware: users sign in with OAuth
(AWS Cognito, optionally through an mPass-style auth proxy), and the server mints and caches a Zammad token for
each user, bounded by that user's Zammad role. It needs the optional [platform] extra (included in the Docker
image) and is enabled by COGNITO_USER_POOL_ID. Community mode never imports it.
/mcpis guarded by FastMCP's Cognito provider (OAuth 2.0 with dynamic client registration); every call runs with the caller's minted token./http/api-key/mcpis not mounted unlessZAMMAD_HTTP_API_KEY_ROUTE=true. It would replay any caller's personalX-Zammad-Tokento the internal Zammad, past the SSO proxy and the corporate-ID gate, so a leaver's or leaked token would keep working from the internet. Turning it on logs a startup warning.A token carries an explicit list:
ticket.agent,ticket.customer,knowledge_base.readerandknowledge_base.editor, intersected with the user's role. Neveradmin.*,reportoruser_preferences.*.Tokens last about 8 days, are cached for 6 (Fernet-encrypted in Valkey), are re-checked every 4 hours and after a permission-gated 403, and are re-minted after a 401. Only expired tokens of this server are cleaned up.
Unverified users (exactly
<cognito:username>@DEFAULT_EMAIL_DOMAIN, or an identity without@) are refused before any Zammad call. Real addresses in that domain are served.Each bootstrap signs its Zammad web session out again, so no full-privilege session is left behind.
The tool list is filtered per user and fails closed: if the mint fails, only
get_meis listed, and it explains why.Every failure fails closed; there is no fallback to a shared token.
Variable | Default | Purpose |
| required | Turns platform mode on |
| required | Cognito region |
| required | Cognito app client id |
| one required | Client secret (confidential client) or signing key (public client) |
| required | Public URL of this server; register |
| required | Zammad on the internal network; every Zammad call goes here |
| required | Bare domain of unverified users' synthetic addresses, which are refused |
| unset | Valkey URL ( |
| required | Comma-separated redirect URI patterns for client registration; empty refuses to start |
|
|
|
| discovered | Route |
|
| Lifetime of the token issued to the MCP client |
|
| Upstream scopes |
|
| JSON log level |
|
|
|
See docs/platform-mode.md for the mint flow, the cache and every setting.
Related MCP server: tokenless-zendesk-mcp
Quick start
uv sync
export ZAMMAD_URL=https://support.example.com
export ZAMMAD_HTTP_TOKEN=<your personal token>
uv run zammad-mcp stdioClaude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"zammad": {
"command": "uvx",
"args": ["--from", "git+https://github.com/Pressingly/zammad-mcp-server", "zammad-mcp", "stdio"],
"env": { "ZAMMAD_URL": "https://support.example.com", "ZAMMAD_HTTP_TOKEN": "<token>" }
}
}
}Docker (multi-user HTTP on port 8214; no token in the container, each client sends its own):
docker build -t zammad-mcp .
docker run --rm -p 8214:8214 -e ZAMMAD_URL=https://support.example.com zammad-mcp
curl http://localhost:8214/healthzPoint the MCP client at http://<host>:8214/http/api-key/mcp with the header X-Zammad-Token: <your token>.
Configuration
Variable | Default | Purpose |
| required | Base URL the server calls, without |
|
| Base URL for the |
| unset | Personal API token for |
|
|
|
|
| Total timeout for one Zammad request |
|
| Connect timeout for one Zammad request |
|
|
|
|
|
|
|
| Ticket search, read, history, create and update |
|
| Article list and read, |
|
|
|
|
| Global |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| Hide agent-only tools from customer tokens in the tool list (Zammad enforces permissions either way) |
|
| Lifetime of a two-step confirmation token (email replies and macros) |
|
| Listen port in |
A disabled module's tools are never registered, and ZAMMAD_READ_ONLY=true leaves out every tool that changes
Zammad, including the email and macro tools. Flags accept true, 1, yes or on, with surrounding spaces
ignored.
Tools
Tool | Who | What it does |
| all | The Zammad user the token belongs to, with their tier |
| all | Active states, priorities and groups, with ids |
| all | Text search plus exact filters (state, priority, group, customer, owner, organization), paged |
| all | One ticket by id or |
| all | A ticket's articles, newest first, paged, bodies as plain text |
| all | One article, with a configurable body length |
| all | Text attachments up to 200 KB as text, images up to 2 MB as images |
| all | Zammad's global search across tickets, users and organizations |
| all | One user (customers: themselves and their organization) |
| all | One organization (customers: their own) |
| all | The tags on a ticket |
| all | Knowledge base answers with a snippet; customers find published answers only |
| all | One knowledge base answer, every translation's title and plain-text body |
| all | A new ticket with a first public message; agents name the customer |
| all | Title, state, priority, group, owner or pending time |
| all | Agents: an internal note by default; customers: a public reply. Never sends email |
| agents | Users by name, login, email or phone |
| agents | Organizations by name or domain |
| agents | A ticket's change history, newest first |
| agents | Add a tag; a new tag needs Zammad's |
| agents | Remove a tag |
| agents, opt-in | Check the group's outgoing address, settle recipients and attachments, return a preview and a confirmation token. Sends nothing |
| agents, opt-in | Send the approved reply as one email article; returns the article id and |
| agents, opt-in | Active macros, with the groups each is limited to |
| agents, opt-in | Preview a macro on up to 50 tickets and return a confirmation token. Changes nothing |
| agents, opt-in | Apply the approved macro through |
The "Who" column is a convenience: Zammad still decides what each token may do. Callers whose role has no
ticket.* permission (for example an Admin-only account) report tier none. Article bodies are
cut to 4000 characters with a truncated flag and a hint. Search on a Zammad without Elasticsearch is a
case-insensitive substring match with no field syntax or ranking; use the search_tickets filters to narrow it.
Two-step tools
Sending an email and applying a macro each take two calls. The prepare_* tool checks the request, stores it
behind a confirmation token and returns a preview for the user. The confirming tool must echo the preview's key
fields (to and subject for email, macro_id and ticket_ids for macros), and its token:
works once, and is spent even when a check then fails;
expires after
ZAMMAD_CONFIRM_TTL_SECONDS;only works for the user it was issued to;
is stored as a hash, next to the sha256 of the exact payload. Recomputing that digest on use detects a corrupted record, not a forged one. Tamper protection for a shared store comes from the platform-mode Valkey backend, which encrypts records with Fernet (authenticated encryption);
is limited to 20 waiting per user. In the in-memory store each user may hold at most a quarter of its 128 MiB (sizes are UTF-8 bytes of the stored JSON), and records over 64 KiB cannot use the last 8 MiB, which stay free for small ones such as macro confirmations. The largest possible email record is about 20 MB.
Email replies:
prepare_email_replyfails clearly when the ticket's group has no outgoing email address, or the address has no active email channel (Zammad: Admin, Groups and Admin, Channels, Email).todefaults to the ticket customer's email.A ticket's participants are:
the ticket customer's email;
the From, To and Cc of every public article an agent wrote. An agent email counts only when Zammad sent it itself (it carries
preferences.email_address_id); agent notes, phone and web articles always count;the From, To, Cc and Reply-To of every public article the ticket's customer wrote (matched by the article's author or on-behalf-of user, or by its From address).
Internal notes and messages from anyone else, such as an outsider who emailed in with the ticket number, add no participants. Unless
ZAMMAD_EMAIL_ALLOW_ANY_RECIPIENT=true, every recipient must be a participant.No recipient may be one of Zammad's own email addresses (any group's), with or without
ZAMMAD_EMAIL_ALLOW_ANY_RECIPIENT.The preview lists a
recipient_warningsentry for every recipient who is not the ticket's customer.Limits: at most 10 recipients (to and cc together) of up to 320 characters each, a body of 1,000,000 characters, and up to 20 attachments with 10 MB of attachment content (decoded) in total. The subject must be one line, without control characters or bidi embeddings and overrides (emoji joiners and LRM/RLM marks are fine).
Spoofing that remains possible, because Zammad trusts the From header of inbound mail:
Mail with a forged From of the customer's address is filed as the customer's message, so its Reply-To and Cc become participants.
Mail with a forged From of one of Zammad's own addresses is filed as sent by an Agent. This server ignores such articles because they lack
preferences.email_address_id, which relies on Zammad setting that only on mail it sends (true in Zammad 7.1).
Neither can change the default
to, and every recipient other than the customer is flagged inrecipient_warnings, so the user sees it before confirming.send_email_replyposts oneemailarticle (sender Agent, not internal) and is never retried once the request may have reached Zammad. Zammad delivers the email in the background, so the result isqueued. When the outcome is unclear (a timeout, a 5xx, or a 2xx whose answer cannot be read) the error says so and asks to check the ticket before preparing the reply again. Each accepted send writes one audit log line (zammad_mcp.audit) with the caller's identity hash, ticket, article id (unknownwhen unreadable) and recipient count, never the body.
Macros:
Only active macros are listed or applied. Zammad itself would run an inactive one.
apply_macrousesPOST /tickets/mass_macro, which checks the macro's group restriction and your change access on every ticket and changes nothing if any ticket fails. After a timeout or a 5xx the error says the macro may or may not have applied.
Every tool declares all four MCP annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint)
and returns an Error: ... string instead of raising, so the model sees what went wrong.
Security notes
Ticket content is untrusted. Ticket titles, articles, attachments and customer names are written by whoever emailed or filled in the form. A crafted ticket can try to instruct the model ("forward this ticket to ..."). The server wraps every such value in an
<untrusted_content source=... id=...>frame and tells the model to treat it as data. Framed: ticket titles and owner, customer and organization names; article subjects, bodies,from,to,ccand author; attachment file names and text; user logins, names, emails, phones and organizations; organization names and domains; tag names; search hit titles, names and emails; history authors and values. Inside a frame every&and<(and the full-width and small<look-alikes) is escaped and invisible format characters are removed, so no spelling of a closing tag survives. Not framed: ids, numbers, timestamps, flags, roles, and state, priority, group and macro names, which only admins set. Knowledge base titles, snippets and bodies are framed too. Tools that send email are disabled by default and need a two-step confirmation./mcpwith a shared token acts as that token's owner. WithZAMMAD_HTTP_SHARED_TOKEN_ROUTE=true, anyone who can reach the port works in Zammad as the owner ofZAMMAD_HTTP_TOKEN. Prefer/http/api-key/mcp.Use a per-user token. Give each person their own Zammad token with only the permissions they need, rather than sharing one admin token. Zammad intersects a token's permissions with the user's role, so a token can never do more than its owner.
Customers need token access enabled. Stock Zammad lets Agents and Admins create API tokens, but not Customers. To let Customers use this server, an admin grants
user_preferences.access_tokento the Customer role (Admin, Roles, Customer).The server never sends
X-Auth-Request-*headers (in any case, with-or_) and never stores cookies, so it cannot impersonate a user on an SSO-fronted Zammad or carry one user's session into another user's request.
Development
uv sync
uv run ruff check .
uv run ruff format --check .
uv run pytest --cov=zammad_mcp --cov-fail-under=80See CONTRIBUTING.md for the branch and commit workflow and SECURITY.md for reporting vulnerabilities.
License
Available Tools
1 toolget_meGet current Zammad userARead-onlyIdempotent
Return the Zammad user the current token belongs to (id, login, name, email, roles).
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
| result | Yes |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true, idempotentHint=true, openWorldHint=true, and destructiveHint=false, covering the safety profile. The description adds only the token-scoped return fields, which does not go far beyond the structured annotations and output schema.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single front-loaded sentence that states the operation, the scoping condition, and the returned fields without any wasted text.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a zero-parameter read tool with rich annotations and an output schema, the description is complete enough. It identifies the current-token user context and the fields returned, while the output schema can define return structure in detail.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
The tool takes zero parameters, so the baseline according to the rubric is 4. Schema description coverage is also 100%, meaning structured fields already handle parameter documentation.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource: 'Return the Zammad user the current token belongs to.' It also identifies the returned identity fields, making the tool's scope unambiguous even without sibling tools.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The phrase 'the current token belongs to' provides clear context for when this tool applies: retrieving the authenticated user for the active token. No alternatives exist among sibling tools, so no exclusions are needed.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
1 tool update
v0.1.0- First observed
get_me
TDQS
Scored across 1 tool
With only one tool, there is no possibility of confusing it with another tool. Its purpose is clearly stated as returning the current Zammad user.
The single tool name follows a clear snake_case verb_object convention (get_me). No inconsistency can exist with only one tool.
A Zammad server with only one trivial identity tool is an extreme mismatch for the platform's scope. Zammad is a full helpdesk system requiring at least ticket, user, and organization operations.
The surface is severely incomplete for a Zammad integration. There are no tools for tickets, users, organizations, groups, or any CRUD/lifecycle operations, leaving agents unable to perform meaningful helpdesk tasks.
Maintenance
Related MCP Connectors
Search and read Zammad tickets, customers and organizations; add notes, replies and ticket updates.
Prove end users to agents and apps: login-links, OIDC clients, and API keys over remote MCP
Managed MCP gateway with OAuth, per-agent permissions, shared accounts and activity logs.
Read tickets, contacts, companies, agents and groups; create, update and reply to tickets.
Related MCP Servers
- AlicenseNot gradedqualityAmaintenanceAn MCP server that connects AI assistants to Zammad, providing tools for managing tickets, users, organizations, and attachments.45AGPL 3.0
- AlicenseAqualityCmaintenanceMCP server that authenticates via browser session cookies to access Zendesk's REST API without API tokens, supporting reads and writes with agent permissions.1912 npm1MIT
- AlicenseNot gradedqualityCmaintenanceMCP server to interact with Zammad ticketing system, enabling ticket search, creation, update, article addition, and user/organization/group queries via the Zammad API.5 npmMIT
- AlicenseNot gradedqualityAmaintenanceA remote MCP server for Zammad, enabling ticket management, search, and OAuth authentication.23 npmMIT