A Model Context Protocol server for Zendesk API integration with AI-powered ticket analysis
Quick Start • Configuration • Tools • Architecture • Development
Zendesk MCP Server provides comprehensive access to the Zendesk API through the Model Context Protocol. It supports two transport modes that are auto-detected from environment variables:
- Stdio mode — API token auth, for CLI-based MCP clients (Claude Code, Cursor, etc.)
- HTTP mode — OAuth 2.1 with PKCE, for web-based MCP clients
Both modes expose the same set of tools. No code changes are needed to switch between them.
Based on mattcoatsworth/zendesk-mcp-server with significant enhancements including AI-powered features, dual-mode authentication, improved error handling, and comprehensive retry logic.
Best for CLI-based MCP clients like Claude Code or Cursor. Each user only needs their own email + API token.
1. Get your API token from Zendesk Admin Center → Apps and integrations → APIs → Zendesk API → Add API token.
2. Configure your MCP client:
{
"zendesk": {
"type": "stdio",
"command": "npx",
"args": ["@sshadows/zendesk-mcp-server"],
"env": {
"ZENDESK_SUBDOMAIN": "your-subdomain",
"ZENDESK_EMAIL": "you@example.com",
"ZENDESK_API_TOKEN": "your-api-token"
}
}
}That's it. The server auto-detects stdio mode and connects.
Best for web-based MCP clients or multi-user deployments with centralized OAuth.
1. Create an OAuth app in Zendesk Admin Center → Apps and integrations → APIs → OAuth Clients.
2. Create a .env file:
ZENDESK_SUBDOMAIN=your-subdomain
ZENDESK_OAUTH_CLIENT_ID=your_client_id
ZENDESK_OAUTH_CLIENT_SECRET=your_client_secret
ZENDESK_OAUTH_REDIRECT_URI=http://localhost:3030/zendesk/oauth/callback3. Start and authorize:
npm start
# Visit http://localhost:3030/oauth/authorize in your browser4. Use the token with your MCP client: Authorization: Bearer mcp_...
| Requirement | Version | Notes |
|---|---|---|
| Node.js | >= 18.0.0 | Required |
| Zendesk Account | Any plan | Required |
| Anthropic API Key | - | Only for AI analysis features |
# npm (recommended)
npm install -g @sshadows/zendesk-mcp-server
# Or from source
git clone https://github.com/SShadowS/zendesk-mcp-server.git
cd zendesk-mcp-server
npm installThe server auto-detects which mode to use based on which variables are set.
Stdio mode (set ZENDESK_EMAIL + ZENDESK_API_TOKEN, without ZENDESK_OAUTH_CLIENT_ID):
ZENDESK_SUBDOMAIN=mycompany
ZENDESK_EMAIL=user@example.com
ZENDESK_API_TOKEN=your-api-tokenHTTP mode (set ZENDESK_OAUTH_CLIENT_ID):
ZENDESK_SUBDOMAIN=mycompany
ZENDESK_OAUTH_CLIENT_ID=your_client_id
ZENDESK_OAUTH_CLIENT_SECRET=your_client_secret
ZENDESK_OAUTH_REDIRECT_URI=http://localhost:3030/zendesk/oauth/callbackCommon (both modes):
MODE=full # 'full' (all 55 tools) or 'lite' (10 essential tools)
ANTHROPIC_API_KEY=sk-ant-... # Required for AI image/document analysis
ZENDESK_DEBUG=false # Enable debug loggingHTTP mode only:
PORT=3030
SERVER_BASE_URL=http://localhost:3030 # Use https:// in productionSee .env.example for the full list.
Control which tools are exposed with the MODE environment variable:
full(default) — All 55 tools available.lite— 10 essential tools for reduced context usage:search,get_user,list_tickets,get_ticket,get_ticket_comments,add_ticket_comment,get_ticket_attachments,analyze_ticket_images,analyze_ticket_documents,get_document_summary.
MODE=lite npm startTicket Management
| Tool | Description |
|---|---|
list_tickets |
List tickets with filters (status, assignee, etc.) |
get_ticket |
Get ticket details with optional comments |
create_ticket |
Create a new ticket |
update_ticket |
Update ticket fields |
get_ticket_comments |
Get all comments on a ticket |
add_ticket_comment |
Add public or internal comment |
get_ticket_attachments |
Get ticket attachments |
analyze_ticket_images |
AI-powered image analysis with Claude Vision |
analyze_ticket_documents |
AI-powered document analysis |
get_document_summary |
Quick document summary |
User Management
| Tool | Description |
|---|---|
list_users |
List all users |
get_user |
Get user details |
create_user |
Create new user |
update_user |
Update user info |
delete_user |
Delete a user |
Organizations
| Tool | Description |
|---|---|
list_organizations |
List all organizations |
get_organization |
Get organization details |
create_organization |
Create new organization |
update_organization |
Update organization |
delete_organization |
Delete organization |
Automation & Workflows
| Category | Tools |
|---|---|
| Groups | list_groups, get_group, create_group, update_group, delete_group |
| Macros | list_macros, get_macro, create_macro, update_macro, delete_macro |
| Views | list_views, get_view, create_view, update_view, delete_view |
| Triggers | list_triggers, get_trigger, create_trigger, update_trigger, delete_trigger |
| Automations | list_automations, get_automation, create_automation, update_automation, delete_automation |
Help Center, Search, Talk & Chat
| Category | Tools |
|---|---|
| Search | search — Search across all Zendesk data |
| Help Center | list_articles, get_article, create_article, update_article, delete_article |
| Talk | get_talk_stats — Phone support statistics |
| Chat | list_chats — Chat conversations |
src/index.js (auto-detection)
├── ZENDESK_EMAIL + ZENDESK_API_TOKEN → Stdio mode
│ ├── ZendeskClient.setApiTokenAuth()
│ ├── setDefaultZendeskClient(client)
│ └── StdioServerTransport (stdin/stdout)
│
└── ZENDESK_OAUTH_CLIENT_ID → HTTP mode
├── Express server (src/http-server.js)
├── OAuth 2.1 with PKCE (src/auth/)
├── Per-session ZendeskClient instances
└── StreamableHTTPServerTransport
Tools are identical in both modes. They call getZendeskClient() which resolves to:
- HTTP mode: Per-session client via AsyncLocalStorage
- Stdio mode: Singleton default client
zendesk-mcp-server/
├── src/
│ ├── index.js # Entry point (auto-detects mode)
│ ├── http-server.js # Express server with OAuth (HTTP mode only)
│ ├── server.js # MCP server setup and tool registration
│ ├── request-context.js # Per-session + default client context
│ ├── auth/
│ │ ├── oauth-handler.js # OAuth 2.1 with PKCE
│ │ ├── session-store.js # Session management
│ │ └── middleware.js # Bearer token auth middleware
│ ├── zendesk-client/
│ │ ├── base.js # Auth, HTTP requests, retry logic
│ │ ├── index.js # Mixin composition
│ │ ├── tickets.js # Ticket API methods
│ │ ├── users.js # User API methods
│ │ └── ... # Other API domain mixins
│ ├── tools/ # MCP tool implementations
│ ├── config/
│ │ └── tool-modes.js # Full/lite mode filtering
│ └── utils/
│ ├── errors.js # Classified error types
│ ├── retry.js # Exponential backoff
│ ├── ticket-context.js # AI prompt context builder
│ ├── document-handler.js # Document routing
│ └── converter-client.js # Office-to-PDF conversion
├── tests/ # Vitest test suite
├── .env.example # Environment variable template
└── CLAUDE.md # AI assistant project guide
- Dual auth in one client:
ZendeskClientBasesupports bothsetApiTokenAuth()(Basic) andsetAccessToken()(Bearer). The_authModefield determines which headergetAuthHeader()returns. - Default client fallback: AsyncLocalStorage doesn't propagate through StdioServerTransport's event callbacks. Instead of fighting that,
getZendeskClient()falls back to a module-level default client in stdio mode. Zero changes needed in any tool file. - Console.error everywhere: In stdio mode, stdout is the MCP transport. All diagnostic logging in shared code paths uses
console.error. - HTTP mode is unchanged:
src/http-server.jsandsrc/auth/*are only imported in HTTP mode. No changes were needed.
npm start # Start server (auto-detects mode)
npm run dev # Start with auto-reload
npm test # Run all tests
npm run test:watch # Run tests in watch mode
npm run inspect # Launch MCP InspectorTests use Vitest and are in tests/ mirroring the src/ directory:
npm test # Run all tests
npm run test:watch # Watch modeIntegration tests (against real Zendesk + Anthropic APIs) require .env credentials and are automatically skipped when credentials are missing.
| Endpoint | Description |
|---|---|
ALL /mcp |
Main MCP endpoint (requires Bearer token) |
GET /oauth/authorize |
Start OAuth flow |
GET /zendesk/oauth/callback |
OAuth callback |
POST /oauth/token |
Token exchange |
POST /oauth/register |
Dynamic client registration (RFC 7591) |
GET /.well-known/oauth-authorization-server |
OAuth metadata (RFC 8414) |
GET /.well-known/oauth-protected-resource |
Protected resource metadata (RFC 9728) |
GET /health |
Health check |
Missing configuration error on startup
The server needs either API token or OAuth credentials. Set one of:
# Stdio mode
ZENDESK_SUBDOMAIN=... ZENDESK_EMAIL=... ZENDESK_API_TOKEN=...
# HTTP mode
ZENDESK_SUBDOMAIN=... ZENDESK_OAUTH_CLIENT_ID=... ZENDESK_OAUTH_CLIENT_SECRET=...401 Unauthorized (OAuth / HTTP mode)
- Complete OAuth flow: visit
http://localhost:3030/oauth/authorize - Check if token expired (24-hour TTL) — re-authorize if needed
- Ensure Bearer token is included:
Authorization: Bearer mcp_xxx - Token format should start with
mcp_
401 Unauthorized (API token / stdio mode)
- Verify
ZENDESK_EMAILis correct - Verify
ZENDESK_API_TOKENis a valid API token (not a password) - Verify
ZENDESK_SUBDOMAINis correct - Check the connection test output in stderr on startup
Session lost after server restart (HTTP mode)
In-memory sessions are cleared on restart. Re-authorize to get a new token. For production, implement a Redis-based session store (see src/auth/session-store.js).
AI analysis features not working
Set ANTHROPIC_API_KEY in your environment. This is only needed for analyze_ticket_images, analyze_ticket_documents, and get_document_summary.
Rate limiting (429 errors)
The server includes exponential backoff retry logic. If you hit rate limits frequently, consider using MODE=lite to reduce API calls, or check if multiple clients share the same credentials.
- Fork the repository
- Create your feature branch (
git checkout -b feature/my-feature) - Run tests (
npm test) - Commit your changes
- Open a Pull Request
MIT License — see LICENSE for details.
- Original implementation by @mattcoatsworth
- Built with Model Context Protocol
- AI features powered by Anthropic Claude
Made with care by SShadowS