Zendesk MCP Server (Extended)
A production-grade MCP server for Zendesk that provides ticket management, search, user/group operations, and knowledge base access with support for local stdio and remote HTTP transports, authentication via API keys or OAuth, and multi-tenancy.
README
Zendesk MCP Server (Extended Edition)
A production-grade Model Context Protocol (MCP) server for Zendesk — run it locally with Claude Desktop over stdio, or deploy it to the cloud as a secure, multi-tenant, OAuth-protected remote MCP server that any AI application can connect to.
Note — extension of the original project
This is an extended fork of reminia/zendesk-mcp-server. The original provides a local, stdio-only Zendesk MCP server with basic ticket tools. This edition keeps full backward compatibility with it (stdio mode, original tools, prompts, and knowledge-base resource) and extends it for remote, internet-facing production use.
Issues with the original repo that this edition addresses
| # | Issue in original | How it's addressed here |
|---|---|---|
| 1 | stdio transport only — could not be reached over a network, so it only worked on the same machine as the AI client | Streamable HTTP transport (/mcp endpoint, MCP spec 2025-03-26+; SSE-as-transport is deprecated and intentionally not used) alongside stdio |
| 2 | No authentication of any kind — anyone who could reach the process could use it | Three auth modes: internal scoped API keys, OAuth 2.1 resource server (generic OIDC + RFC 9728 discovery), or both simultaneously |
| 3 | No permission model — every caller could read and write tickets | 4-layer read/write control: per-tool scopes (fail-closed), tool-list filtering, human-in-the-loop confirmation, dual Zendesk identity backstop |
| 4 | Blocking I/O inside async handlers — sync Zenpy calls froze the event loop under concurrent HTTP load | All Zendesk calls run in worker threads (run_in_thread) |
| 5 | Deprecated offset pagination — Zendesk is sunsetting it; comments were not paginated at all (context blowout on long tickets) | Cursor pagination (page[size]/page[after]) for tickets and comments |
| 6 | Sparse Zendesk coverage — only 5 tools; user/assignee IDs could not be resolved, custom fields were opaque, no search | 14 tools including search, users, groups, ticket-field metadata, KB article search, attachment upload |
| 7 | Single Zendesk account hard-wired at startup from .env |
Multi-tenancy: per-tenant Zendesk credentials, keys/OAuth claims mapped to tenants, hot-swappable connection settings |
| 8 | No deployment story — no TLS, no health check, stdio-oriented Docker image | Docker (HTTP-first, healthcheck), docker-compose with Caddy auto-TLS, Terraform for EC2 and for ECS Fargate + ALB |
| 9 | No admin tooling — key rotation/credential changes required editing .env and restarting |
Web admin console (separate port) + zendesk-keys CLI; connection hot-swap without restart |
| 10 | No tests | 38 unit tests + HTTP/admin/OAuth smoke test suites; tests run in CI |
Feature overview
Original features (retained):
- Ticket tools: get ticket, list tickets, get comments, create ticket, update ticket, comment on ticket
- Image attachment download with security hardening (MIME allowlist, magic-byte validation, 10 MB cap)
- Prompts:
analyze-ticket,draft-ticket-response - Resource:
zendesk://knowledge-base(all Help Center articles, cached 1 h) - stdio transport for Claude Desktop / Claude Code local use
New in this edition:
- Streamable HTTP transport with
/healthendpoint - Internal API keys:
zmk_prefix, SHA-256 hashed at rest, scopes, expiry, instant revocation, audit log - OAuth 2.1 resource server: JWKS/issuer/audience JWT validation, RFC 9728 Protected Resource
Metadata at
/.well-known/oauth-protected-resource/mcp— works with Auth0, Descope, Cognito, Keycloak, WorkOS - Scope model:
tickets:read,tickets:write,kb:read,*— enforced per tool, fail-closed, with tools/list filtering - Optional elicitation: in-client approve/decline before posting public (customer-visible) comments
- Dual Zendesk identity: reads via a restricted user (e.g. light agent), writes via a full agent
- Multi-tenancy with per-tenant Zendesk credentials
- Web admin console: connection settings (hot-swap + test), tenant management, key lifecycle
- New tools:
search_tickets,get_user,search_users,list_groups,list_ticket_fields,search_articles,upload_attachment - Tool annotations (
readOnlyHint/destructiveHint) and structured output on all tools - Deployment: Dockerfile, docker-compose + Caddy (auto-TLS), Terraform for EC2 and ECS Fargate
- MCP Registry manifest (
server.json) and publishing guide
Tools
| Tool | Scope | Description |
|---|---|---|
get_ticket |
tickets:read | Get a ticket by ID |
get_tickets |
tickets:read | List tickets (cursor pagination, sortable) |
get_ticket_comments |
tickets:read | Ticket comments incl. attachment metadata (cursor pagination) |
search_tickets |
tickets:read | Zendesk search syntax, e.g. status:open priority:high |
get_ticket_attachment |
tickets:read | Download an image attachment (base64, validated) |
get_user |
tickets:read | Resolve a user ID to name/email/role |
search_users |
tickets:read | Search users by name or email |
list_groups |
tickets:read | Agent groups (teams) for routing |
list_ticket_fields |
tickets:read | Field metadata — interpret/set custom_fields {id, value} |
search_articles |
kb:read | Search Help Center articles (context-safe) |
create_ticket |
tickets:write | Create a ticket |
update_ticket |
tickets:write | Update status/priority/assignee/tags/custom fields/due date |
create_ticket_comment |
tickets:write | Comment on a ticket (optional elicitation for public comments; supports attachments) |
upload_attachment |
tickets:write | Upload a file (≤10 MB), returns token for comment attachment |
Prompts: analyze-ticket, draft-ticket-response. Resource: zendesk://knowledge-base (kb:read).
Quick start (local, stdio — same as the original)
git clone <this-repo>
cd zendesk-mcp-server
uv venv && uv pip install -e .
cp .env.example .env # fill in ZENDESK_SUBDOMAIN, ZENDESK_EMAIL, ZENDESK_API_KEY
Claude Desktop config:
{
"mcpServers": {
"zendesk": {
"command": "uv",
"args": ["--directory", "/path/to/zendesk-mcp-server", "run", "zendesk"]
}
}
}
stdio mode is trusted-local: no auth, full access, identical behavior to the original repo.
Remote mode (Streamable HTTP)
MCP_TRANSPORT=http zendesk # serves http://0.0.0.0:8000/mcp + /health
Create scoped API keys (shown once, stored hashed):
zendesk-keys create --name "reader" --scopes tickets:read,kb:read
zendesk-keys create --name "agent" --scopes tickets:read,tickets:write,kb:read --expires-days 30
zendesk-keys create --name "admin" --scopes "*"
zendesk-keys list
zendesk-keys revoke --id 2
Connect a client:
claude mcp add zendesk --transport http https://mcp.example.com/mcp \
--header "Authorization: Bearer zmk_..."
Read-only keys never see write tools in tools/list; write calls without
tickets:write are denied; unknown tools are denied by default (fail closed).
Configuration reference
All configuration is via environment variables (or .env; the admin console can
override connection settings at runtime, persisted in the key-store DB).
Zendesk connection
| Variable | Required | Default | Description |
|---|---|---|---|
ZENDESK_SUBDOMAIN |
yes | — | <subdomain>.zendesk.com |
ZENDESK_API_KEY |
yes | — | Zendesk API token |
ZENDESK_EMAIL |
yes* | — | Single-identity mode: agent email paired with the token |
ZENDESK_READ_EMAIL |
no | ZENDESK_EMAIL |
Dual identity: restricted user (light agent) for all reads |
ZENDESK_WRITE_EMAIL |
no | ZENDESK_EMAIL |
Dual identity: full agent for all writes |
*Either ZENDESK_EMAIL or both ZENDESK_READ_EMAIL/ZENDESK_WRITE_EMAIL.
Zendesk roles live on the user, not the token — pairing the same token with a
restricted user email yields restricted permissions (Layer 4 backstop).
Transport
| Variable | Default | Description |
|---|---|---|
MCP_TRANSPORT |
stdio |
stdio (local, trusted) or http (remote) |
MCP_HOST |
0.0.0.0 |
HTTP bind address |
MCP_PORT |
8000 |
HTTP port; MCP endpoint is /mcp |
Authentication
| Variable | Default | Description |
|---|---|---|
MCP_AUTH_ENABLED |
true |
Set false only for trusted private networks |
MCP_AUTH_MODE |
keys |
keys | oauth | both |
MCP_KEYS_DB |
data/keys.db |
SQLite store for keys, tenants, config, audit log |
MCP_PUBLIC_URL |
— | Public base URL (required for oauth/both; used in RFC 9728 metadata) |
MCP_OAUTH_ISSUER |
— | OIDC issuer, e.g. https://your-tenant.auth0.com/ |
MCP_OAUTH_AUDIENCE |
MCP_PUBLIC_URL |
Audience/identifier of this server at the IdP |
MCP_OAUTH_JWKS_URI |
<issuer>/.well-known/jwks.json |
Override if your IdP differs |
MCP_OAUTH_AUTH_SERVERS |
issuer | Comma-separated authorization server URLs |
MCP_OAUTH_TENANT_CLAIM |
zendesk_tenant |
JWT claim naming the caller's tenant (id or name) |
Admin console & safety
| Variable | Default | Description |
|---|---|---|
MCP_ADMIN_PASSWORD |
— (disabled) | Setting it enables the admin console |
MCP_ADMIN_HOST |
127.0.0.1 |
Keep loopback; reach via SSH/SSM tunnel |
MCP_ADMIN_PORT |
9000 |
Admin console port (never expose publicly) |
MCP_WRITE_CONFIRMATION |
false |
Elicit user approval before PUBLIC comments (Layer 3) |
Security model
| Layer | Mechanism |
|---|---|
| Edge | TLS 1.2+ (Caddy or ALB/ACM), security headers, 80/443 only |
| AuthN | API keys (hashed, expiring, revocable) and/or OAuth 2.1 JWTs (PKCE at the IdP) |
| L1 AuthZ | Central TOOL_PERMISSIONS map, enforced pre-dispatch, fail-closed |
| L2 Visibility | tools/list filtered to caller's scopes — models can't attempt what they can't see |
| L3 Confirmation | destructiveHint annotations + optional elicitation for public comments |
| L4 Zendesk | Dual identity — reads through a restricted Zendesk user, writes through a full agent |
| Admin | Separate loopback port, password + CSRF, secrets never re-displayed |
| Audit | Append-only log: key lifecycle, admin actions, logins, writes |
Multi-tenancy
By default every caller uses the server's own Zendesk connection. To let other teams/customers connect their Zendesk:
- Admin console → Tenants → add name, subdomain, API token, read/write emails.
- Bind credentials to the tenant: create an API key with that tenant selected
(or
zendesk-keys create ... --tenant-id N), or configure your IdP to issue the tenant's name/id in theMCP_OAUTH_TENANT_CLAIMJWT claim. - All tool calls from that identity are routed to the tenant's Zendesk. Deleting a tenant revokes its keys immediately.
Web admin console
Enable with MCP_ADMIN_PASSWORD. Reach it via tunnel — never expose it:
ssh -L 9000:localhost:9000 user@host # or the SSM equivalent, see docs/DEPLOYMENT.md
# open http://localhost:9000
Provides: Zendesk connection editor with hot-swap (no restart) and "test connection", tenant management, API key create/revoke with scope checkboxes and expiry, and key usage visibility. Stored tokens are never re-displayed; new keys are shown exactly once.
Deployment
Docker (single container):
docker build -t zendesk-mcp-server .
docker run --rm -p 8000:8000 --env-file .env -v zmcp-keys:/data zendesk-mcp-server
Docker Compose + automatic TLS (recommended single-box): set MCP_DOMAIN in
.env, then docker compose up -d — Caddy terminates TLS with Let's Encrypt and
proxies to the server. Create keys with
docker compose exec zendesk-mcp zendesk-keys create ....
AWS EC2 (Terraform): terraform/ provisions EC2 (SSM access, no SSH, IMDSv2),
security group (80/443 only), Elastic IP, optional Route53, and bootstraps
Docker + the repo. Full runbook: docs/DEPLOYMENT.md.
Windows Server without Docker: native Python + Caddy with your own TLS certificate + NSSM services, including a troubleshooting FAQ of real-world Windows issues (Node/PATH, cert chains, arg mangling): docs/WINDOWS_SETUP.md.
AWS ECS Fargate + ALB (scale-out): terraform/ecs/ provisions ECR, Fargate
service, ALB with ACM/TLS 1.3, EFS-backed key store, Secrets Manager injection,
and CloudWatch. Keep desired_count=1 until the key store is migrated off SQLite.
MCP Registry: fill in server.json and follow docs/REGISTRY.md
to publish your deployed server to registry.modelcontextprotocol.io.
Development
uv sync --extra dev
uv run pytest tests/ -v # 38 tests: tools, keystore, permissions, tenancy, auth modes
Project layout:
src/zendesk_mcp_server/
server.py # FastMCP app: tools, prompts, resources, transports
zendesk_client.py # Zendesk API client (dual identity, cursor pagination)
auth.py # API-key verifier, OIDC JWT verifier, auth-mode factory
permissions.py # Scope model + enforcement/filtering middleware
keystore.py # SQLite: keys, tenants, audit log
runtime.py # Client holder: hot-swap + per-tenant routing
admin.py # Web admin console (separate port)
keys_cli.py # zendesk-keys CLI
config.py # Env-based settings
terraform/ # EC2 deployment terraform/ecs/ # Fargate deployment
docs/ # Architecture plan, deployment runbook, registry guide
Architecture decisions and full history: docs/REMOTE_MCP_ARCHITECTURE_PLAN.md.
License
Apache 2.0 — same as the original project. Original work by reminia; extensions as described above.
推荐服务器
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 模型以安全和受控的方式获取实时的网络信息。