An MCP server that connects AI assistants to Zammad, providing tools for managing tickets, users, organizations, attachments, and the Knowledge Base.
Disclaimer: This project is not affiliated with or endorsed by Zammad GmbH or the Zammad Foundation. This is an independent integration that uses the Zammad API.
-
Ticket Management
zammad_search_tickets- Search tickets with multiple filterszammad_get_ticket- Get detailed ticket information with articles (supports pagination)zammad_create_ticket- Create new ticketszammad_update_ticket- Update ticket propertieszammad_add_article- Add comments/notes to ticketszammad_add_ticket_tag/zammad_remove_ticket_tag- Manage ticket tagszammad_get_ticket_tags- Get tags assigned to a specific ticketzammad_list_tags- List all tags defined in the system (requires admin.tag permission)
-
Attachment Support
zammad_get_article_attachments- List attachments for a ticket articlezammad_download_attachment- Download attachment content (base64-encoded)zammad_delete_attachment- Delete attachments from ticket articles
-
User & Organization Management
zammad_get_user/zammad_search_users- User information and searchzammad_get_organization/zammad_search_organizations- Organization datazammad_get_current_user- Get authenticated user info
-
System Information
zammad_list_groups- Get all available groups (cached for performance)zammad_list_ticket_states- Get all ticket states (cached for performance)zammad_list_ticket_priorities- Get all priority levels (cached for performance)zammad_get_ticket_stats- Get ticket statistics (optimized with pagination)
-
Knowledge Base (requires
knowledge_base.reader/knowledge_base.editorpermission)zammad_list_knowledge_bases- List all knowledge baseszammad_get_knowledge_base- Get details of a knowledge base (category/answer IDs, locale IDs)zammad_get_kb_category- Get a category with child/answer IDszammad_create_kb_category- Create a new category (root or nested)zammad_update_kb_category- Update category title, parent, or iconzammad_delete_kb_category- Permanently delete a categoryzammad_list_kb_answers- List all answers in a category (with titles)zammad_search_kb_answers- Search answers by title or body content across all categories (or a specific one)zammad_get_kb_answer- Get answer details including title, body content, and attachmentszammad_create_kb_answer- Create a new answer (starts in draft)zammad_update_kb_answer- Update answer title, body, or move to another categoryzammad_delete_kb_answer- Permanently delete an answerzammad_publish_kb_answer- Publish an answer publiclyzammad_internalize_kb_answer- Make an answer internal (agents only)zammad_archive_kb_answer- Archive an answer (hidden but recoverable)zammad_unarchive_kb_answer- Restore an archived answer to draftzammad_add_kb_answer_attachment- Upload an attachment to an answer (from file path or base64)zammad_delete_kb_answer_attachment- Delete an attachment from an answerzammad_download_kb_answer_attachment- Download an attachment to a local path on the Mac
Access Zammad data directly:
zammad://ticket/{id}- Individual ticket detailszammad://user/{id}- User profile informationzammad://organization/{id}- Organization detailszammad://queue/{group}- Ticket queue for a groupzammad://kb/{kb_id}- Knowledge base overviewzammad://kb/{kb_id}/category/{category_id}- Knowledge base categoryzammad://kb/{kb_id}/answer/{answer_id}- Knowledge base answer
Pre-configured prompts:
analyze_ticket- Comprehensive ticket analysisdraft_response- Generate ticket responsesescalation_summary- Summarize escalated tickets
Run without installation:
# Install uv if you haven't already
# macOS/Linux:
curl -LsSf https://astral.sh/uv/install.sh | sh
# Windows:
powershell -c "irm https://astral.sh/uv/install.ps1 | iex"
# Run directly from GitHub
uvx --from git+https://github.com/basher83/zammad-mcp.git mcp-zammad
# Or with environment variables
ZAMMAD_URL=https://your-instance.zammad.com/api/v1 \
ZAMMAD_HTTP_TOKEN=your-api-token \
uvx --from git+https://github.com/basher83/zammad-mcp.git mcp-zammadFor production or containerized deployments:
# Basic usage with environment variables
docker run --rm -i \
-e ZAMMAD_URL=https://your-instance.zammad.com/api/v1 \
-e ZAMMAD_HTTP_TOKEN=your-api-token \
ghcr.io/basher83/zammad-mcp:latest
# If you must skip TLS verification (self-signed / internal CA), add:
# -e ZAMMAD_INSECURE=true
# Using Docker secrets for better security
docker run --rm -i \
-e ZAMMAD_URL=https://your-instance.zammad.com/api/v1 \
-e ZAMMAD_HTTP_TOKEN_FILE=/run/secrets/token \
-v ./secrets/zammad_http_token.txt:/run/secrets/token:ro \
ghcr.io/basher83/zammad-mcp:latest
# With .env file
docker run --rm -i \
--env-file .env \
ghcr.io/basher83/zammad-mcp:latestThe project publishes Docker images with semantic versioning:
latest- Most recent stable release1.2.3- Specific version (recommended for production)1.2- Latest patch of 1.2 minor release1- Latest minor/patch of 1.x major releasemain- Latest main branch (may be unstable)
# Recommended for production - pin to specific version
docker pull ghcr.io/basher83/zammad-mcp:1.0.0View all versions on GitHub Container Registry.
To contribute or modify the code:
# Clone the repository
git clone https://github.com/basher83/zammad-mcp.git
cd zammad-mcp
# Run the setup script
# On macOS/Linux:
./setup.sh
# On Windows (PowerShell):
.\setup.ps1For manual setup, see the Development section below.
The server requires Zammad API credentials. Use a .env file:
-
Copy the example configuration:
cp .env.example .env
-
Edit
.envwith your Zammad credentials:# Required: Zammad instance URL (include /api/v1) ZAMMAD_URL=https://your-instance.zammad.com/api/v1 # Authentication (choose one method): # Option 1: API Token (recommended) ZAMMAD_HTTP_TOKEN=your-api-token # Option 2: OAuth2 Token # ZAMMAD_OAUTH2_TOKEN=your-oauth2-token # Option 3: Username/Password # ZAMMAD_USERNAME=your-username # ZAMMAD_PASSWORD=your-password # Optional: Disable TLS certificate verification (NOT recommended for production) # Truthy values only: 1, true, yes, on. Unset (default) keeps TLS verification enabled. # ZAMMAD_INSECURE=true # Optional: Logging level (default: INFO) # Valid values: DEBUG, INFO, WARNING, ERROR, CRITICAL # LOG_LEVEL=INFO # Optional: Transport Configuration # MCP_TRANSPORT=stdio # Transport type: stdio (default) or http # MCP_HOST=127.0.0.1 # Host address for HTTP transport # MCP_PORT=8000 # Port number for HTTP transport
-
The server will automatically load the
.envfile on startup.
| Variable | Default | Description |
|---|---|---|
MCP_TRANSPORT |
stdio |
Transport type: stdio or http |
MCP_HOST |
127.0.0.1 |
Host address for HTTP transport |
MCP_PORT |
- | Port number for HTTP transport (required if MCP_TRANSPORT=http) |
Important: Keep your .env file out of version control (already in .gitignore).
When enabled, the server intercepts all Zammad API calls and anonymizes personally identifiable information before it reaches the AI assistant — and restores original values when the AI references them back in tool inputs.
Zammad API ──► PIIFilteringClient ──► Claude
replaces PII with sees [PERSON_1],
pseudonyms [EMAIL_1], etc.
Claude ──► PIIFilteringClient ──► Zammad API
restores originals receives real values
from pseudonyms
Two complementary strategies run on every API response:
Structural fields (anonymized by key name, bypassing NLP):
| Zammad field | Pseudonym type |
|---|---|
email, login, from, to, customer, created_by |
[EMAIL_N] |
firstname, lastname, owner |
[PERSON_N] |
phone, mobile, fax |
[PHONE_N] |
Free text (detected by spaCy NER + Presidio, multilingual EN/DE/FR/ES/IT/PL):
| Entity | Pseudonym | Confidence threshold |
|---|---|---|
PERSON |
[PERSON_N] |
0.85 |
EMAIL_ADDRESS |
[EMAIL_N] |
0.70 |
PHONE_NUMBER |
[PHONE_N] |
0.50 |
Dates and locations are intentionally not anonymized — they are context metadata, not PII, in a support-ticket setting.
Known-persons list: at startup the server fetches all Zammad users and builds a fast regex-based name matcher (case-insensitive, German umlaut normalization, @mention support). This catches names in free text and informal greetings that NLP would miss. The list refreshes every 30 minutes in the background.
URL protection: any URL found in a field value is preserved verbatim — no word inside a URL is ever anonymized.
Product names: alphanumeric model codes (DC485S, MH1504P, …) are protected automatically. Additional names can be listed in mcp_zammad/product_names.txt (bundled) or supplied via environment variable.
The mapping is kept in memory for the lifetime of the server process — no external storage required. The same entity always gets the same pseudonym within a session (Alice Johnson → [PERSON_1] consistently across all tool calls).
1. Clone the core library into the vendor directory:
git clone https://git.b.picoquant.com/ruettinger/llm-anon-core.git vendor/llm-anon-core2. Install with the pii extra:
uv sync --extra pii3. Set the environment variable:
PII_FILTER_ENABLED=true{
"mcpServers": {
"zammad": {
"command": "uv",
"args": ["run", "--directory", "/path/to/Zammad-MCP", "mcp-zammad"],
"env": {
"ZAMMAD_URL": "https://your-instance.zammad.com/api/v1",
"ZAMMAD_HTTP_TOKEN": "your-api-token",
"PII_FILTER_ENABLED": "true"
}
}
}
}| Variable | Default | Description |
|---|---|---|
PII_FILTER_ENABLED |
false |
Set to true to enable PII anonymization |
PII_PRODUCT_NAMES_FILE |
mcp_zammad/product_names.txt |
Path to a text file of product/brand names to never anonymize (one per line, # comments supported) |
PII_PRODUCT_NAMES |
— | Comma-separated additional product names; merged with the file above |
PII_REFRESH_INTERVAL |
1800 |
Seconds between known-persons list refreshes from the Zammad user DB |
mcp_zammad/product_names.txt (committed to the repo) lists product and brand names that must never be anonymized. Edit it to match your catalogue. Alphanumeric model codes (DC485S, MH1504P) are handled automatically and don't need to be listed.
# mcp_zammad/product_names.txt
FLIMbee
SepiaII
TimeHarp
To use a custom file outside the repo set PII_PRODUCT_NAMES_FILE in your MCP server config.
When PII_FILTER_ENABLED is not set the behaviour is identical to the unmodified server — no performance overhead, no changed output.
All data-returning tools support two output formats:
- Markdown (default): Human-readable format optimized for LLM consumption
- JSON: Machine-readable format with complete metadata
Example:
# Markdown (default)
zammad_search_tickets(query="network", response_format="markdown")
# JSON
zammad_search_tickets(query="network", response_format="json")Add to your Claude Desktop configuration:
{
"mcpServers": {
"zammad": {
"command": "uvx",
"args": ["--from", "git+https://github.com/basher83/zammad-mcp.git", "mcp-zammad"],
"env": {
"ZAMMAD_URL": "https://your-instance.zammad.com/api/v1",
"ZAMMAD_HTTP_TOKEN": "your-api-token"
}
}
}
}Or using Docker:
{
"mcpServers": {
"zammad": {
"command": "docker",
"args": ["run", "--rm", "-i",
"-e", "ZAMMAD_URL=https://your-instance.zammad.com/api/v1",
"-e", "ZAMMAD_HTTP_TOKEN=your-api-token",
"ghcr.io/basher83/zammad-mcp:latest"]
}
}
}Note: The server supports stdio (default) and HTTP transports. Stdio mode requires the -i flag for Docker. See the HTTP Transport section below for remote deployments.
Important: The -i flag is required—without it, the MCP server cannot receive stdin. Preserve this flag in wrapper scripts or shell aliases.
Or if you have it installed locally:
{
"mcpServers": {
"zammad": {
"command": "python",
"args": ["-m", "mcp_zammad"],
"env": {
"ZAMMAD_URL": "https://your-instance.zammad.com/api/v1",
"ZAMMAD_HTTP_TOKEN": "your-api-token"
}
}
}
}# Run the server
python -m mcp_zammad
# Or with environment variables
ZAMMAD_URL=https://instance.zammad.com/api/v1 ZAMMAD_HTTP_TOKEN=token python -m mcp_zammadThe server supports Streamable HTTP transport for remote deployments.
Set these environment variables to enable HTTP transport:
export MCP_TRANSPORT=http # Enable HTTP transport
export MCP_HOST=127.0.0.1 # Host to bind (default: 127.0.0.1)
export MCP_PORT=8000 # Port to listen onDirect Python:
MCP_TRANSPORT=http \
MCP_HOST=127.0.0.1 \
MCP_PORT=8000 \
ZAMMAD_URL=https://your-instance.zammad.com/api/v1 \
ZAMMAD_HTTP_TOKEN=your-api-token \
uvx --from git+https://github.com/basher83/zammad-mcp.git mcp-zammadDocker:
docker run -d \
--name zammad-mcp-http \
-p 8000:8000 \
-e MCP_TRANSPORT=http \
-e MCP_HOST=0.0.0.0 \
-e MCP_PORT=8000 \
-e ZAMMAD_URL=https://your-instance.zammad.com/api/v1 \
-e ZAMMAD_HTTP_TOKEN=your-api-token \
ghcr.io/basher83/zammad-mcp:latestAccess the MCP endpoint at http://localhost:8000/mcp/.
0.0.0.0 only behind a reverse proxy with TLS.
Use a reverse proxy (nginx/Caddy) for HTTPS and security:
Example with Caddy:
# Start the MCP server (binds to all interfaces for reverse proxy)
MCP_TRANSPORT=http \
MCP_HOST=0.0.0.0 \
MCP_PORT=8000 \
ZAMMAD_URL=https://your-instance.zammad.com/api/v1 \
ZAMMAD_HTTP_TOKEN=your-api-token \
uvx --from git+https://github.com/basher83/zammad-mcp.git mcp-zammadCaddyfile configuration:
mcp.yourdomain.com {
reverse_proxy localhost:8000
# Caddy automatically handles HTTPS/TLS
}Production checklist:
- Use
MCP_HOST=0.0.0.0only behind a reverse proxy - Enable HTTPS/TLS via reverse proxy
- Implement authentication at the proxy or application layer
- Restrict access with firewall rules
Configure your MCP client to use HTTP transport:
{
"mcpServers": {
"zammad": {
"url": "http://localhost:8000/mcp/"
}
}
}- Local Development: Use
MCP_HOST=127.0.0.1(localhost only) - Production: Implement authentication (see Security)
- HTTPS: Use reverse proxy for TLS
- Firewall: Restrict access to trusted networks
- DNS Rebinding: Built-in origin validation protects against these attacks
Use search_tickets with state="open" to find all open tickets
Use create_ticket with:
- title: "Customer needs help with login"
- group: "Support"
- customer: "customer@example.com"
- article_body: "Customer reported unable to login..."
1. Use get_ticket with ticket_id=123 to see the full conversation
2. Use add_article to add your response
3. Use update_ticket to change state to "pending reminder"
Use the escalation_summary prompt to get a report of all tickets approaching escalation
Use add_article with attachments parameter:
- ticket_id: 123
- body: "See attached documentation"
- attachments: [
{
"filename": "guide.pdf",
"data": "JVBERi0xLjQKJ...", # base64-encoded content
"mime_type": "application/pdf"
}
]
Use delete_attachment with:
- ticket_id: 123
- article_id: 456
- attachment_id: 789
Use zammad_search_kb_answers with:
- kb_id: 1
- query: EasyTau light source
Returns all answers whose title or body contains the search string. Use the returned answer ID with zammad_get_kb_answer to read the full content.
Use zammad_download_kb_answer_attachment with:
- kb_id: 1
- answer_id: 58
- attachment_id: 97727
- save_path: /Users/you/Downloads/attachment.pdf
The file is written directly to your Mac. No binary data enters the context window. To view or process the file, drag it into the Claude Desktop chat window.
Use zammad_add_kb_answer_attachment with:
- kb_id: 1
- answer_id: 58
- file_path: /Users/you/Downloads/figure.png
filename and mime_type are inferred automatically from the path. Alternatively pass filename + data (base64-encoded content) if the file is not on disk.
# Clone the repository
git clone https://github.com/basher83/zammad-mcp.git
cd zammad-mcp
# Run the setup script
# On macOS/Linux:
./setup.sh
# On Windows (PowerShell):
.\setup.ps1# Clone the repository
git clone https://github.com/basher83/zammad-mcp.git
cd zammad-mcp
# Create a virtual environment with uv
uv venv
# Activate the virtual environment
# On macOS/Linux:
source .venv/bin/activate
# On Windows:
# .venv\Scripts\activate
# Install in development mode
uv pip install -e ".[dev]"
# Optional: enable PII anonymization support
git clone https://git.b.picoquant.com/ruettinger/llm-anon-core.git vendor/llm-anon-core
uv sync --extra piizammad-mcp/
├── mcp_zammad/
│ ├── __init__.py
│ ├── __main__.py
│ ├── server.py # MCP server implementation
│ ├── client.py # Zammad API client wrapper
│ ├── models.py # Pydantic models
│ ├── pii_client.py # Optional PII anonymization proxy (opt-in)
│ └── product_names.txt # Bundled product/brand names excluded from PII masking
├── vendor/ # Local deps (git-ignored); clone llm-anon-core here
├── tests/
├── scripts/
│ └── uv/ # UV single-file scripts
├── pyproject.toml
├── README.md
├── Dockerfile
└── .env.example
# Install development dependencies
uv pip install -e ".[dev]"
# Run tests
uv run pytest
# Run with coverage
uv run pytest --cov=mcp_zammad# Format code
uv run ruff format mcp_zammad tests
# Lint
uv run ruff check mcp_zammad tests
# Type checking
uv run mypy mcp_zammad
# Run all quality checks
./scripts/quality-check.shTo generate an API token in Zammad:
- Log into your Zammad instance
- Click on your avatar → Profile
- Navigate to "Token Access"
- Click "Create"
- Name your token (e.g., "MCP Server")
- Select appropriate permissions
- Copy the generated token
- Verify your Zammad URL includes the protocol (https://)
- Check that your API token has the necessary permissions
- Ensure your Zammad instance is accessible from your network
- For self-signed/internal certs only: set
ZAMMAD_INSECURE=trueto bypass TLS verification
- Use API tokens over username/password
- Ensure tokens have permissions for the operations
- Check token expiration in Zammad settings
The server respects Zammad's rate limits. If you hit rate limits:
- Reduce request frequency
- Paginate large result sets
- Cache frequently accessed data
The server implements multiple layers of protection following industry best practices.
Report via GitHub Security Advisories (preferred) or see SECURITY.md.
- ✅ Input Validation: Validates and sanitizes all user inputs (models.py)
- ✅ SSRF Protection: URL validation prevents server-side request forgery (client.py)
- ✅ XSS Prevention: Sanitizes HTML in all text fields (models.py)
- ✅ Secure Authentication: Prefers API tokens over passwords (client.py)
- ✅ Dependency Scanning: Dependabot detects vulnerabilities automatically
- ✅ Security Testing: CI runs Bandit, Safety, and pip-audit (security-scan.yml)
See SECURITY.md for complete documentation.
See CONTRIBUTING.md for development setup, code standards, testing, and pull request guidelines.
AGPL-3.0-or-later — matches the Zammad project license.
- ARCHITECTURE.md — Technical design
- SECURITY.md — Security policy
- CONTRIBUTING.md — Development guidelines
- CHANGELOG.md — Version history
"Zammad" is a trademark of Zammad GmbH. This independent integration is not affiliated with or endorsed by Zammad GmbH or the Zammad Foundation. The name "Zammad" indicates compatibility with the Zammad ticket system.