@fruggr/zendesk-mcp-server 1.7.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -7,17 +7,18 @@
7
7
  [![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen?logo=renovatebot&logoColor=white)](https://renovatebot.com)
8
8
  [![semantic-release](https://img.shields.io/badge/semantic--release-e10079?logo=semantic-release&logoColor=white)](https://github.com/semantic-release/semantic-release)
9
9
 
10
- A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that connects LLMs to the **Zendesk Support & Help Center APIs** — with per-user OAuth 2.1 PKCE authentication and fine-grained tool visibility controls.
10
+ A [Model Context Protocol](https://modelcontextprotocol.io) (MCP) server that connects LLMs to the **Zendesk Support & Help Center APIs** — with per-user OAuth 2.1 PKCE authentication and fine-grained tool visibility controls. Runs locally over **stdio** or as a private **remote MCP server** over HTTP.
11
11
 
12
12
  ## Why this server?
13
13
 
14
14
  Most Zendesk integrations use a shared admin API key, giving every user full access to every ticket. This server takes a different approach:
15
15
 
16
- - **Per-user authentication** — Each user authenticates with their own Zendesk credentials via OAuth 2.1 PKCE. No shared admin key, no elevated privileges. The LLM sees exactly what the user is allowed to see.
16
+ - **Per-user authentication by default** — In both transports, the default is OAuth 2.1 PKCE: each user authenticates with their own Zendesk credentials, so the LLM sees exactly what the user is allowed to see. A static API-token escape hatch is documented below for stdio-only CI / headless contexts; it's refused at boot in HTTP mode.
17
+ - **Two deployment shapes, same auth story** — Run it on your laptop as a stdio MCP server (Claude Desktop / Claude Code / VS Code) or deploy it as a private remote MCP server with one user, one Zendesk session per HTTP request.
17
18
  - **Context-friendly tool modes** — Expose 37 individual tools, 3 namespace proxies, or a single unified tool. Choose the mode that fits your LLM's context budget.
18
19
  - **Section-based article editing** — For large Help Center articles, read and rewrite one section at a time (parsed by h1/h2/h3 headings) instead of shuffling the full HTML body through the LLM. Reduces tokens by 10–100× on targeted edits.
19
20
  - **Read-only mode** — Restrict the server to read operations only, ideal for assistants that should never modify data.
20
- - **Zero runtime dependencies beyond the MCP SDK** — Built on `@modelcontextprotocol/sdk` and `zod`. No Express, no heavyweight frameworks.
21
+ - **Lean stack** — Built on the official `@modelcontextprotocol/sdk` plus `zod`.
21
22
 
22
23
  > Built and maintained by [Digital4better](https://digital4better.com) for the [Fruggr](https://www.fruggr.io) project.
23
24
 
@@ -28,13 +29,19 @@ Most Zendesk integrations use a shared admin API key, giving every user full acc
28
29
  - You want an LLM to read or triage **Zendesk tickets** and **Help Center articles** on behalf of a real user, with that user's own permissions — not a shared admin key.
29
30
  - You're editing **large Help Center articles** and want section-scoped reads/rewrites instead of round-tripping the full HTML body through the model.
30
31
  - You need to **cap the tool surface** — read-only assistants, a single namespace, or one unified tool to fit a tight context budget.
31
- - You run a **stdio MCP client** (Claude Desktop, Claude Code, Cursor, VS Code, Cline, …) and want a `npx`-installable server with no extra infrastructure.
32
+ - You run a **stdio MCP client** (Claude Desktop, Claude Code, Cursor, VS Code, Cline, …) and want a `npx`-installable server with no extra infrastructure, **or** you want to **deploy it as a private remote MCP server** that web/native clients reach over HTTP — each MCP client still carries its own user's OAuth token.
32
33
 
33
34
  **Look elsewhere when:**
34
35
 
35
36
  - You need Zendesk products outside Support & Guide (e.g. Talk, Explore analytics, Sell) — those endpoints aren't covered.
36
- - You want a hosted/remote HTTP server: this one speaks stdio and runs next to the client.
37
- - You need a single shared service account for all users — that's the opposite of this server's per-user OAuth model (use API-token auth if you must, but one identity then applies to everyone).
37
+ - You need a single shared service account for all users — that's the opposite of this server's per-user OAuth model (the API-token escape hatch exists for stdio CI only, and is refused at boot in HTTP).
38
+
39
+ ## Use cases
40
+
41
+ | Persona | Transport | Auth | Quick start |
42
+ |---------|-----------|------|-------------|
43
+ | **Run it on your laptop** — single user, plugged into Claude Desktop / Claude Code / VS Code | `stdio` (default) | OAuth 2.1 PKCE in your browser (or API token for CI) | [Quick start: local](#quick-start-local-stdio) |
44
+ | **Deploy a private remote MCP server** — one server per Zendesk account, each MCP client carries its own user's OAuth token | `http` | Per-user OAuth 2.1 PKCE bearer in `Authorization:` header; API token refused | [Quick start: remote](#quick-start-remote-http) |
38
45
 
39
46
  ## Tool modes
40
47
 
@@ -143,102 +150,197 @@ zendesk-mcp-server acme --namespace tickets
143
150
  > Contributors and maintainers run the toolchain on a newer Node + pnpm —
144
151
  > see [Development](#development).
145
152
 
146
- ## Installation
153
+ ## Quick start: local (stdio)
154
+
155
+ The default mode. One developer, one Zendesk account, OAuth 2.1 PKCE in the browser.
156
+
157
+ ### Install
147
158
 
148
159
  ```bash
149
160
  # Run without installing
150
161
  npx -y @fruggr/zendesk-mcp-server <your-subdomain>
162
+
163
+ # Or install globally
164
+ npm install -g @fruggr/zendesk-mcp-server
165
+ zendesk-mcp-server <your-subdomain>
151
166
  ```
152
167
 
153
- Or install globally:
168
+ > Cloning from source and running a development branch is covered in the [Development](#development) section.
169
+
170
+ ### Zendesk OAuth setup
171
+
172
+ 1. Go to **Admin Center → Apps and integrations → APIs → OAuth Clients**
173
+ 2. Create a **public** client:
174
+ - **Identifier**: `<your-subdomain>_zendesk` (or set `ZENDESK_OAUTH_CLIENT_ID`)
175
+ - **Redirect URL**: `http://localhost:27439/callback` (change the port to match
176
+ `ZENDESK_OAUTH_CALLBACK_PORT` / `--callback-port` if you override it; Zendesk
177
+ accepts several redirect URLs, one per line)
178
+
179
+ ### Run
154
180
 
155
181
  ```bash
156
- npm install -g @fruggr/zendesk-mcp-server
157
182
  zendesk-mcp-server <your-subdomain>
158
183
  ```
159
184
 
160
- Or clone and run locally:
185
+ On the first tool call, the server starts the sign-in flow: it opens a browser
186
+ window **and** returns a tool message containing the authorize URL. The call
187
+ does not block waiting for sign-in — authenticate in the browser (or open the
188
+ URL manually if it didn't open), then retry the request.
161
189
 
162
- ```bash
163
- git clone https://github.com/fruggr/zendesk-mcp-server.git
164
- cd zendesk-mcp-server
165
- pnpm install
166
- pnpm build
167
- node dist/index.js <your-subdomain>
190
+ Once authenticated, the token is **persisted to disk** (one owner-only `0600`
191
+ file per subdomain in your OS config dir —
192
+ `%APPDATA%\fruggr\zendesk-mcp-server\<subdomain>.json` on Windows,
193
+ `${XDG_CONFIG_HOME:-~/.config}/fruggr/zendesk-mcp-server/<subdomain>.json`
194
+ elsewhere; override the path with `ZENDESK_TOKEN_FILE`). It is reused across restarts, so you don't
195
+ re-authenticate every time the MCP client respawns the server. If the Zendesk
196
+ OAuth client has token expiration enabled, the stored refresh token is used to
197
+ renew access silently; only an expired/invalid refresh token triggers a new
198
+ browser sign-in.
199
+
200
+ > **Port conflict?** If port `27439` is already in use the first tool call returns
201
+ > a clear error telling you to set `ZENDESK_OAUTH_CALLBACK_PORT` (or
202
+ > `--callback-port`) to a free port — remember to register the matching
203
+ > `http://localhost:<port>/callback` redirect URL in your Zendesk OAuth client.
204
+
205
+ > **API token escape hatch (stdio only).** For headless/CI environments where a browser is unavailable, set `ZENDESK_EMAIL` + `ZENDESK_API_TOKEN` (generate the token in **Admin Center → Apps and integrations → APIs → Zendesk API → Token Access**). The MCP server then uses Basic auth instead of starting the OAuth flow. This mode is **refused at boot in HTTP** because a shared static credential would expose every caller to the issuing user's rights.
206
+
207
+ ### MCP client wiring
208
+
209
+ <details>
210
+ <summary><strong>Claude Desktop</strong></summary>
211
+
212
+ Add to your `claude_desktop_config.json`:
213
+
214
+ ```json
215
+ {
216
+ "mcpServers": {
217
+ "zendesk": {
218
+ "command": "npx",
219
+ "args": ["-y", "@fruggr/zendesk-mcp-server", "<your-subdomain>", "--mode", "single"]
220
+ }
221
+ }
222
+ }
168
223
  ```
169
224
 
170
- Or run a development branch directly from GitHub (handy for testing PRs without publishing to npm) — the `prepare` script builds the package automatically on install:
225
+ </details>
226
+
227
+ <details>
228
+ <summary><strong>Claude Code</strong></summary>
171
229
 
172
230
  ```bash
173
- # Latest main
174
- npx -y github:fruggr/zendesk-mcp-server <your-subdomain>
231
+ claude mcp add zendesk -- npx -y @fruggr/zendesk-mcp-server <your-subdomain> --mode single
232
+ ```
175
233
 
176
- # A specific branch / tag / commit
177
- npx -y github:fruggr/zendesk-mcp-server#my-feature-branch <your-subdomain>
234
+ </details>
235
+
236
+ <details>
237
+ <summary><strong>VS Code (Copilot / Continue / Cline)</strong></summary>
238
+
239
+ Add to your `.vscode/mcp.json`:
240
+
241
+ ```json
242
+ {
243
+ "servers": {
244
+ "zendesk": {
245
+ "command": "npx",
246
+ "args": ["-y", "@fruggr/zendesk-mcp-server", "<your-subdomain>", "--mode", "single"]
247
+ }
248
+ }
249
+ }
178
250
  ```
179
251
 
180
- ## Authentication
252
+ </details>
181
253
 
182
- The server supports two authentication methods:
254
+ ## Quick start: remote (HTTP)
183
255
 
184
- ### Option A: OAuth 2.1 PKCE (recommended)
256
+ > 🧪 **Experimental.** The HTTP transport is shipped but has not yet been
257
+ > exercised end-to-end against a real Zendesk tenant from every supported
258
+ > MCP client. Local stdio is the supported path. Until this notice is
259
+ > removed, expect rough edges around OAuth discovery behind reverse
260
+ > proxies, CORS with browser clients, and 401 / refresh flows — please
261
+ > open an issue with the symptoms you hit.
185
262
 
186
- No API key needed. Each user authenticates via their browser on the first tool call.
263
+ Deploy a private MCP server for **one** Zendesk account. Every MCP client connecting to the server presents its **own** user's OAuth bearer in `Authorization:` the server never sees a shared admin key.
187
264
 
188
- **Zendesk setup:**
265
+ ### Zendesk OAuth setup
189
266
 
190
- 1. Go to **Admin Center > Apps and integrations > APIs > OAuth Clients**
191
- 2. Create a public client:
192
- - **Identifier**: `<your-subdomain>_zendesk` (or set `ZENDESK_OAUTH_CLIENT_ID`)
193
- - **Redirect URL**: `http://localhost:3000/callback`
267
+ Same procedure as the [local quick start](#zendesk-oauth-setup), with one difference: the **Redirect URL** must match the callback your MCP client uses — provided by the client itself, e.g. `https://claude.ai/oauth/callback` for claude.ai on the web. Check your client's docs.
194
268
 
195
- **Run:**
269
+ ### Run the server
196
270
 
197
271
  ```bash
198
- zendesk-mcp-server <your-subdomain>
272
+ zendesk-mcp-server <your-subdomain> --transport http --port 3000 \
273
+ --public-url https://mcp.example.com
274
+ # stderr: Zendesk MCP server running via http on 0.0.0.0:3000
199
275
  ```
200
276
 
201
- On the first tool call, the server starts the sign-in flow: it opens a browser
202
- window **and** returns a tool message containing the authorize URL. The call
203
- does not block waiting for sign-in — authenticate in the browser (or open the
204
- URL manually if it didn't open), then retry the request. Once authenticated, the
205
- token is cached in memory and subsequent calls succeed for the session.
277
+ ### Public URL
206
278
 
207
- ### Option B: API token
279
+ `--public-url` (or `PUBLIC_URL=…`) is the URL **clients use to reach you**. It's what gets advertised in the OAuth discovery metadata as the canonical resource identifier (RFC 8707). When the server is behind a TLS reverse proxy — Azure App Service, Heroku, Fly.io, Cloudflare Tunnel, nginx, Caddy… — the bind host and the public URL differ, and spec-compliant MCP clients will refuse the connection if the metadata advertises the wrong resource. Without it the server boots in a degraded mode and prints a warning.
280
+
281
+ | Platform | Recommended setup |
282
+ |---|---|
283
+ | **Azure App Service** | Startup command: `PUBLIC_URL="https://$WEBSITE_HOSTNAME" zendesk-mcp-server $ZENDESK_SUBDOMAIN --transport http --port $PORT` |
284
+ | **Heroku / Fly / Cloud Run** | `PUBLIC_URL=https://<your-app>.<provider>.app` in the env / config |
285
+ | **Caddy / nginx / Traefik in front of a VM** | `PUBLIC_URL=https://mcp.example.com` |
286
+ | **Local dev (no proxy)** | `--host 127.0.0.1 --port 3000` — the resource URL is derived automatically (the wildcard `0.0.0.0` is what triggers the warning) |
208
287
 
209
- For headless/CI environments or quick testing.
288
+ ### Authentication on every request
210
289
 
211
- **Zendesk setup:**
290
+ `Authorization: Bearer …` is required on **every** `/mcp` request — a session id alone is never accepted as a credential. The most recent bearer presented on a session is the one used for Zendesk calls, so a client refreshing its token mid-session just works.
212
291
 
213
- 1. Go to **Admin Center > Apps and integrations > APIs > Zendesk API**
214
- 2. Enable **Token Access**, create a token
292
+ ### Verify discovery endpoints
215
293
 
216
- **Run:**
294
+ Served by the HTTP transport in `src/transports/http.ts`:
217
295
 
218
296
  ```bash
219
- ZENDESK_EMAIL=you@example.com ZENDESK_API_TOKEN=dneib123... \
220
- zendesk-mcp-server <your-subdomain>
297
+ curl -s http://localhost:3000/.well-known/oauth-protected-resource
298
+ # → { "authorization_servers": ["https://<subdomain>.zendesk.com"], ... }
299
+
300
+ curl -s http://localhost:3000/.well-known/oauth-authorization-server
301
+ # → { "issuer": "https://<subdomain>.zendesk.com", "authorization_endpoint": "...", ... }
302
+
303
+ curl -s -i http://localhost:3000/healthz # → 200 OK
221
304
  ```
222
305
 
223
- ## Configuration
306
+ ### MCP client wiring
307
+
308
+ Every major MCP client supports remote servers over Streamable HTTP and handles the OAuth 2.1 PKCE discovery flow natively — paste the URL, sign in once, you're connected. Replace `https://mcp.example.com` below with your deployed origin.
309
+
310
+ <details>
311
+ <summary><strong>Claude Code (CLI)</strong></summary>
312
+
313
+ ```bash
314
+ claude mcp add zendesk --transport http https://mcp.example.com/mcp
315
+ ```
224
316
 
225
- ### MCP client configuration
317
+ </details>
226
318
 
227
319
  <details>
228
320
  <summary><strong>Claude Desktop</strong></summary>
229
321
 
230
- Add to your `claude_desktop_config.json`:
322
+ **Settings → Connectors → + Add custom connector**, paste `https://mcp.example.com/mcp`, click **Connect**. Claude Desktop drives the OAuth flow in your browser on first call.
323
+
324
+ </details>
325
+
326
+ <details>
327
+ <summary><strong>claude.ai (web)</strong></summary>
328
+
329
+ **Settings → Connectors → Add custom connector**, same URL. The OAuth flow runs in the same tab.
330
+
331
+ </details>
332
+
333
+ <details>
334
+ <summary><strong>VS Code (GitHub Copilot / Continue / Cline)</strong></summary>
335
+
336
+ Add to your `.vscode/mcp.json`:
231
337
 
232
338
  ```json
233
339
  {
234
- "mcpServers": {
340
+ "servers": {
235
341
  "zendesk": {
236
- "command": "npx",
237
- "args": ["-y", "@fruggr/zendesk-mcp-server", "<your-subdomain>", "--mode", "single"],
238
- "env": {
239
- "ZENDESK_EMAIL": "you@example.com",
240
- "ZENDESK_API_TOKEN": "your-api-token"
241
- }
342
+ "type": "http",
343
+ "url": "https://mcp.example.com/mcp"
242
344
  }
243
345
  }
244
346
  }
@@ -247,31 +349,25 @@ Add to your `claude_desktop_config.json`:
247
349
  </details>
248
350
 
249
351
  <details>
250
- <summary><strong>Claude Code</strong></summary>
352
+ <summary><strong>Cursor, Windsurf</strong></summary>
251
353
 
252
- ```bash
253
- claude mcp add zendesk -- npx -y @fruggr/zendesk-mcp-server <your-subdomain> --mode single
254
- ```
255
-
256
- For API token auth, set the env vars before launching Claude Code or add them to your shell profile.
354
+ Both expose an MCP settings UI that accepts a remote URL. Paste `https://mcp.example.com/mcp` and sign in when prompted.
257
355
 
258
356
  </details>
259
357
 
260
358
  <details>
261
- <summary><strong>VS Code (Copilot / Continue / Cline)</strong></summary>
359
+ <summary><strong>Zed</strong></summary>
262
360
 
263
- Add to your `.vscode/mcp.json`:
361
+ Zed added native OAuth 2.0 + PKCE for Streamable HTTP MCP servers in 2026 ([zed-industries/zed#51768](https://github.com/zed-industries/zed/pull/51768)). Configure the remote server in your Zed settings; on first use Zed opens a loopback browser callback to complete the flow.
362
+
363
+ If you're on an older Zed build that predates that change, fall back to [`mcp-remote`](https://github.com/geelen/mcp-remote) as a local shim that does the OAuth flow on your machine and proxies the session:
264
364
 
265
365
  ```json
266
366
  {
267
- "servers": {
367
+ "context_servers": {
268
368
  "zendesk": {
269
369
  "command": "npx",
270
- "args": ["-y", "@fruggr/zendesk-mcp-server", "<your-subdomain>", "--mode", "single"],
271
- "env": {
272
- "ZENDESK_EMAIL": "you@example.com",
273
- "ZENDESK_API_TOKEN": "your-api-token"
274
- }
370
+ "args": ["-y", "mcp-remote", "https://mcp.example.com/mcp"]
275
371
  }
276
372
  }
277
373
  }
@@ -279,7 +375,33 @@ Add to your `.vscode/mcp.json`:
279
375
 
280
376
  </details>
281
377
 
282
- ### CLI reference
378
+ On the first call the MCP client fetches the discovery metadata, performs the OAuth 2.1 PKCE flow against Zendesk on behalf of the **end user**, and sends the resulting access token as a `Bearer` to the server. Each subsequent tool call runs with that user's Zendesk permissions.
379
+
380
+ ### CORS
381
+
382
+ The HTTP transport ships a default CORS allowlist that covers today's major **browser-based** MCP clients out of the box (ordered by user base): `chatgpt.com`, `claude.ai`, `gemini.google.com`, `copilot.microsoft.com`, `perplexity.ai`, `chat.mistral.ai`, `grok.com`, plus `chat.openai.com`. Localhost on any port (MCP Inspector, dev pages) is also always allowed.
383
+
384
+ **Native MCP clients** (Claude Desktop / Claude Code CLI / Cursor / VS Code / Zed) send no `Origin` header — CORS doesn't apply to them, they work regardless.
385
+
386
+ To allow an additional browser origin (custom dashboard, internal portal), pass `--cors-origin` (repeatable) or set `CORS_ORIGIN` as a comma-separated list:
387
+
388
+ ```bash
389
+ zendesk-mcp-server acme --transport http --port 3000 \
390
+ --cors-origin https://internal-dashboard.example.com \
391
+ --cors-origin https://team-portal.example.com
392
+ ```
393
+
394
+ The defaults are always applied — your additions extend them, they don't replace them.
395
+
396
+ ### Operator responsibilities
397
+
398
+ This server provides the MCP transport and the OAuth discovery metadata. The operator is still responsible for:
399
+
400
+ - **TLS termination** (put the server behind a reverse proxy like Caddy / nginx / Cloudflare Tunnel)
401
+ - **Network exposure & firewall** (the server binds `0.0.0.0` by default — choose carefully)
402
+ - **Process supervision** (systemd, Docker, fly.io, your hosting provider's runner — none is shipped here)
403
+
404
+ ## CLI reference
283
405
 
284
406
  ```
285
407
  zendesk-mcp-server <subdomain> [options]
@@ -290,6 +412,15 @@ Options:
290
412
  --tool <name> Filter by tool name (repeatable, forces --mode all)
291
413
  --read-only Only expose read operations
292
414
  --log-level <level> debug | info (default) | warn | error
415
+ --transport <t> stdio (default) | http
416
+ --host <host> HTTP bind host (default: 0.0.0.0)
417
+ --port <port> HTTP bind port (default: 3000; 0 = OS-assigned)
418
+ --public-url <url> Public URL clients use to reach the server (HTTP mode,
419
+ required behind a TLS reverse proxy)
420
+ --cors-origin <url> Extra browser origin allowed by CORS (repeatable;
421
+ adds to the default allowlist of major web MCP
422
+ clients + localhost-any-port)
423
+ --callback-port <port> Local OAuth callback port for stdio (default 27439)
293
424
  ```
294
425
 
295
426
  `--namespace` and `--read-only` are applied before the proxies are registered, so they narrow the surface in every mode — in the default `namespace` mode, `--namespace help_center` registers a single proxy (`zendesk_help_center`) instead of three.
@@ -297,7 +428,7 @@ Options:
297
428
  **Examples:**
298
429
 
299
430
  ```bash
300
- # Single tool mode — minimal context, all 37 operations in one tool
431
+ # Local single-tool mode — minimal context, all 37 operations in one tool
301
432
  zendesk-mcp-server acme --mode single
302
433
 
303
434
  # Read-only tickets only
@@ -305,19 +436,30 @@ zendesk-mcp-server acme --read-only --namespace tickets
305
436
 
306
437
  # Cherry-pick specific tools
307
438
  zendesk-mcp-server acme --tool get_ticket --tool search_tickets --tool get_current_user
439
+
440
+ # Remote HTTP, read-only Help Center surface
441
+ zendesk-mcp-server acme --transport http --port 8080 \
442
+ --namespace help_center --read-only
308
443
  ```
309
444
 
310
- ### Environment variables
445
+ ## Environment variables
311
446
 
312
447
  | Variable | Required | Default | Description |
313
448
  |----------|----------|---------|-------------|
314
449
  | `ZENDESK_SUBDOMAIN` | yes (or CLI arg) | — | Zendesk subdomain (e.g., `acme` for acme.zendesk.com) |
315
450
  | `ZENDESK_OAUTH_CLIENT_ID` | no | `<subdomain>_zendesk` | OAuth client identifier |
316
- | `ZENDESK_EMAIL` | for API token auth | | Agent email for Basic auth |
317
- | `ZENDESK_API_TOKEN` | for API token auth | | Zendesk API token |
451
+ | `ZENDESK_OAUTH_CALLBACK_PORT` | no | `27439` | Local port for the OAuth browser callback (also `--callback-port`). Must match the redirect URL registered in Zendesk. **stdio only**. |
452
+ | `ZENDESK_TOKEN_FILE` | no | OS config dir | Path to the persisted OAuth token file (`0600`). |
453
+ | `ZENDESK_EMAIL` | stdio API-token only | — | Agent email for Basic auth — **refused in HTTP** |
454
+ | `ZENDESK_API_TOKEN` | stdio API-token only | — | Zendesk API token — **refused in HTTP** |
455
+ | `TRANSPORT` | no | `stdio` | `stdio` or `http` |
456
+ | `HOST` | no | `0.0.0.0` | HTTP bind host |
457
+ | `PORT` | no | `3000` | HTTP bind port (`0` to let the OS pick) |
458
+ | `PUBLIC_URL` | recommended in HTTP behind a proxy | derived from host:port | Public URL advertised in OAuth discovery metadata |
459
+ | `CORS_ORIGIN` | no | — | Comma-separated browser origins added to the default CORS allowlist |
318
460
  | `LOG_LEVEL` | no | `info` | Log verbosity (`debug` surfaces the full OAuth flow trace) |
319
461
 
320
- If both `ZENDESK_EMAIL` and `ZENDESK_API_TOKEN` are set, the server uses API token auth. Otherwise, it uses OAuth 2.1 PKCE.
462
+ In stdio, if both `ZENDESK_EMAIL` and `ZENDESK_API_TOKEN` are set, the server uses API token auth; otherwise it uses OAuth 2.1 PKCE. In HTTP mode, API token credentials are refused at boot — only per-user OAuth 2.1 PKCE is accepted. Full API-token setup is documented in [`docs/api-token-stdio.md`](docs/api-token-stdio.md).
321
463
 
322
464
  ## Troubleshooting
323
465
 
@@ -340,6 +482,22 @@ When the browser fails to open, look for the `oauth_browser_open_failed` event:
340
482
  it reports the underlying error, the platform, and which environment markers are
341
483
  present (no secrets, tokens, or env values are ever logged).
342
484
 
485
+ ### The OAuth callback port is already in use
486
+
487
+ The sign-in flow runs a short-lived local server on port `27439` to receive the
488
+ callback. If that port is taken, the first tool call fails with a message saying
489
+ so (and logs `oauth_callback_listen_failed`). Pick a free port with
490
+ `ZENDESK_OAUTH_CALLBACK_PORT=<port>` (or `--callback-port <port>`), and register
491
+ the matching `http://localhost:<port>/callback` redirect URL in your Zendesk
492
+ OAuth client.
493
+
494
+ ### I have to re-authenticate every time
495
+
496
+ The OAuth token is persisted to an owner-only file in your OS config dir and
497
+ reused across restarts, so this shouldn't happen. If it does, check that the file
498
+ is writable (`ZENDESK_TOKEN_FILE` to relocate it) and look for
499
+ `token_persist_failed` in the logs.
500
+
343
501
  Where each client writes the server's stderr:
344
502
 
345
503
  | Client | Log location |
@@ -364,26 +522,30 @@ test the project. The **published package** still runs on Node 20+ (see
364
522
  and runs the smoke test to keep that promise honest.
365
523
 
366
524
  ```bash
367
- # Install dependencies
368
- pnpm install
525
+ # Clone, install, build
526
+ git clone https://github.com/fruggr/zendesk-mcp-server.git
527
+ cd zendesk-mcp-server && pnpm install && pnpm build
528
+ node dist/index.js <your-subdomain>
369
529
 
370
- # Dev mode (auto-reload)
371
- ZENDESK_EMAIL=you@example.com ZENDESK_API_TOKEN=xxx \
372
- pnpm dev -- <your-subdomain> --mode all
530
+ # Dev mode, OAuth (browser opens on first tool call)
531
+ pnpm dev -- <your-subdomain> --mode all
373
532
 
374
- # Build
375
- pnpm build
533
+ # Dev mode, HTTP transport (OAuth bearer from the MCP client)
534
+ pnpm dev -- <your-subdomain> --transport http --port 3000 --public-url http://localhost:3000
376
535
 
377
- # Type-check
378
- pnpm typecheck
536
+ # Build / typecheck / lint / test
537
+ pnpm build && pnpm typecheck && pnpm check && pnpm test
538
+ ```
379
539
 
380
- # Lint
381
- pnpm check
540
+ To test a PR branch without publishing to npm — the `prepare` script builds on install:
382
541
 
383
- # Tests
384
- pnpm test
542
+ ```bash
543
+ npx -y github:fruggr/zendesk-mcp-server <your-subdomain>
544
+ npx -y github:fruggr/zendesk-mcp-server#my-feature-branch <your-subdomain>
385
545
  ```
386
546
 
547
+ Contributor conventions (architecture, code style, submission bar, release workflow) live in [`AGENTS.md`](AGENTS.md).
548
+
387
549
  ## Inspiration & related projects
388
550
 
389
551
  This project was built with reference to: