freshdesk-mcp-server
Click on "Install 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., "@freshdesk-mcp-serverWhich tickets are waiting on us? Read the top one and draft a reply as an internal note."
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.
freshdesk-mcp-server
An MCP server for Freshdesk, built around the part every other Freshdesk integration gets wrong: knowing which tickets actually need an answer.
npx -y freshdesk-mcp-server19 tools over the Freshdesk REST API v2 — read tickets with their full thread, move fields, draft internally, reply to customers — with two opinions baked in:
Ticket status does not tell you whose turn it is. A ticket can sit in "In Progress" and still owe the customer a reply for four days. This server works that out from the reply timestamps instead of guessing from status.
Your account is not the demo account. Statuses, custom fields and their choices are discovered from your Freshdesk, never hardcoded.
Why another one
There are several Freshdesk MCP servers. This one exists because of four specific things.
The queue is computed, not guessed. GET /tickets has two defaults that individually look reasonable and together hide exactly the work you care about:
Without
updated_sinceit filters by creation date over the last 30 days. A ticket opened two months ago whose customer replied this morning is simply absent — and ordering bycreated_atburies it even when it is present.2=Open, 3=Pendingare the statuses Freshdesk ships. An account with custom statuses keeps its live work elsewhere, so filtering on the defaults returns zero tickets while looking perfectly correct.
Both are regression-tested here. freshdesk_find_unanswered_tickets asks for updated_since + order_by=updated_at, and reads include=stats to compare agent_responded_at against requester_responded_at — so classifying a whole page of 100 tickets costs no extra API call.
Nothing is hardcoded. The status codes, priorities, ticket types and custom fields come from GET /ticket_fields. You can filter and write using names ("Esperando confirmación", accents optional) and an invalid value fails locally, listing the valid ones, instead of returning an opaque 400.
It respects a shared, small budget. Freshdesk's rate limit is per account, not per key, so every integration you run draws from the same bucket — and per-endpoint ceilings are lower than the account one (ticket listing is capped at 20/min on entry plans). This server throttles client-side, honours Retry-After, reports the remaining budget in errors, and asks for per_page=100 so a result set costs the fewest possible calls.
It never hides who sees a write. Every tool says whether the customer receives an email, sees it on the portal, or never knows it happened.
Related MCP server: Freshdesk MCP Server
Quickstart
Get your key: Freshdesk → your avatar → Profile Settings → "Your API Key".
Claude Code
claude mcp add freshdesk \
--env FRESHDESK_DOMAIN=yourcompany.freshdesk.com \
--env FRESHDESK_API_KEY=your_key \
-- npx -y freshdesk-mcp-serverClaude Desktop, Cursor, or anything else with an mcpServers block
{
"mcpServers": {
"freshdesk": {
"command": "npx",
"args": ["-y", "freshdesk-mcp-server"],
"env": {
"FRESHDESK_DOMAIN": "yourcompany.freshdesk.com",
"FRESHDESK_API_KEY": "your_key"
}
}
}
}Then ask for the queue:
Which tickets are waiting on us? Read the top one and draft a reply as an internal note.
Configuration
Variable | Required | Default | What it does |
| yes | — |
|
| yes | — | Profile Settings → Your API Key |
| no |
| Status codes that mean "nobody is waiting on us". Read the note below |
| no |
| Client-side throttle. Lower it if other integrations share the account |
| no |
| Cap on pages walked per listing. Truncation is always reported, never silent |
| no |
| Retries on 429 and 5xx |
| no |
| Per-request timeout |
About FRESHDESK_CLOSED_STATUSES
This has to be configuration rather than detection, and it is worth 30 seconds of your attention. GET /ticket_fields lists your custom statuses with their labels but says nothing about which of them are terminal, and guessing from a label breaks the moment your account is not in English. Resolved (4) and Closed (5) are the two Freshdesk ships, so they are the default.
Run freshdesk_get_ticket_fields once and look at your statuses. If you have your own terminal state — a "Merged" or "Cancelled" — name it:
FRESHDESK_CLOSED_STATUSES=4,5,10Get this wrong and tickets nobody is waiting on will show up in the queue. An unknown status is deliberately treated as open: surfacing a ticket that needed no answer is cheap, hiding one that did is not.
Tools
Reading
Tool | What it does |
| Start here. The queue of tickets awaiting our reply, most recently prodded first |
| Recent tickets, filtered by status name or code |
| One ticket: fields, requester, company, reply timestamps, original request |
| The whole thread, each message labelled by author and visibility |
| The filter endpoint, for questions a listing cannot express |
| Your account's real statuses, priorities, types and custom fields |
| Who is asking, and for which account |
| That person's history — "have they asked this before?" |
| Parent, children and trackers, so a recurring incident stays one thing |
| The documented answer, from your solution articles |
| Wording your team already approved |
Writing
Tool | Does the customer see it? | Annotations |
| No email. Status changes are visible on the portal |
|
| Never. Internal only |
|
| Visible on the portal, no email sent |
|
| Yes — it emails them. Cannot be unsent |
|
| No |
|
Bodies accept markdown or plain text and are converted to the HTML Freshdesk expects. This matters more than it sounds: a body sent with raw \n newlines arrives in the customer's inbox as one unbroken paragraph, because Freshdesk drops the string straight into an HTML email.
Resources and prompts
freshdesk://instance/fields— your field inventory, loadable once as context instead of a tool call per session.freshdesk://instance/account— account name and plan (the plan sets your rate limits).Prompt
draft_reply— the full workflow: find what is waiting → read the thread → check history → ground it in the KB and approved wording → leave the draft where a human can approve it.Prompt
triage_ticket— read a ticket and propose field moves, with a reason for each.
Safety model
Be clear-eyed about this before you point it at a production help desk.
A Freshdesk API key is account-wide and cannot be scoped. The same key that reads tickets can close them and email your customers. The only real granularity available is the Freshdesk role of the agent who owns the key — so if you want a read-mostly setup, create a limited agent in Freshdesk and use their key. This server cannot fence off what the key can do.
Tool annotations are advisory. destructiveHint asks your MCP client to confirm before calling; a client that does not prompt will let a model act without friction. Do not treat the hints as a control.
So freshdesk_reply_to_customer is two-step. Called without confirm, it returns exactly what would be sent — recipient, subject, rendered HTML — and sends nothing:
PREVIEW — nothing was sent.
Ticket: #4821 Invoice export missing March rows
Would email: Dana Okafor <dana@example.com>
Body as Freshdesk would render it:
<p>Hi Dana,</p><p>Thanks for the update.</p>
To send this for real, call freshdesk_reply_to_customer again with confirm: true.A retried or mistaken tool call therefore costs a preview, not an email to somebody's customer.
When a human should approve the wording, do not send at all. Freshdesk has no draft concept in its API, but a private note is exactly that: the text lands on the ticket for a person to read, edit and send themselves. That is what draft_reply uses by default.
Every write is logged to stderr with a timestamp, the tool, the ticket and the size — never to stdout, which carries the MCP protocol.
The API key is never logged. It is stripped from error text before it leaves the process, because Freshdesk echoes request context into some error bodies and one leak into a client's logs means rotating a credential that opens the whole account.
Troubleshooting
401 — the key is wrong, revoked, or belongs to a different domain than FRESHDESK_DOMAIN.
403 — the key is valid but the owning agent's Freshdesk role does not allow the operation. This is a permissions change in Freshdesk, not something to retry.
429 — you hit the account-wide limit. It is shared with every other integration on the account; lower FRESHDESK_REQUESTS_PER_MINUTE. Note that each include side-load costs extra credit, so a listing is rarely one call's worth.
A filter returns nothing but the tickets clearly exist — you are almost certainly filtering on statuses your account does not use. Run freshdesk_get_ticket_fields.
Closed tickets in the queue — set FRESHDESK_CLOSED_STATUSES to include your own terminal statuses.
Resolving a ticket fails with a 400 naming a field — your account marks that field required_for_closure. freshdesk_get_ticket_fields lists which ones, and freshdesk_update_ticket warns before the call.
Why not Freshdesk's own /mcp endpoint?
Freshdesk hosts an MCP server at https://<domain>.freshdesk.com/mcp, but it is OAuth-only: it answers WWW-Authenticate: Bearer resource_metadata=… and rejects any static token with a 403. That is fine for a client that runs an interactive OAuth flow and refreshes tokens, and unusable for anything wiring up a long-lived credential. Freshdesk's REST API v2 authenticates with an API key that does not expire, which is what this server uses.
A useful diagnostic in general: before assuming a /mcp endpoint takes a token, call it with no auth and read the WWW-Authenticate header.
Development
npm install
npm run build # tsc; the bin keeps its shebang
npm test # 71 tests, no network
npm run typecheck
npm run lint # biome
npm run inspect # build + @modelcontextprotocol/inspector over stdioTests stub fetch and never touch a real Freshdesk. Three of them are regression tests for bugs that are easy to reintroduce: a custom-status account returning an empty queue, a 40-day-old ticket with a fresh customer reply being ranked below a young quiet one, and a 429 being propagated instead of retried.
One more guards the write surface. WRITE_TOOL_NAMES in src/tools/index.ts is maintained by hand, and a test compares it against the annotations the tools actually declare. Adding a mutating tool without declaring it destructive fails the suite — which is the point. A security-relevant list that is derived from another list is not a list, it is a default.
Built on @modelcontextprotocol/server v2 with Zod v4 schemas, over stdio. Node ≥ 20.
Releasing
Releases go out through npm trusted publishing (OIDC): there is no NPM_TOKEN in this
repository and no long-lived write token anywhere. release.yml exchanges a GitHub Actions
OIDC token for a short-lived credential scoped to this repo and workflow, and npm attaches a
provenance attestation automatically.
That leaves one wrinkle, worth knowing before you look for the setting: the first version of
a package cannot be published with OIDC. The trusted-publisher configuration lives on the
package's own settings page, and that page does not exist until the package does. So 0.1.0
is published by hand, once:
npm login # browser + 2FA
npm run lint && npm run typecheck && npm test && npm run build
npm publish # no --provenance: attestations need a CI OIDC contextThen, on npmjs.com → the package → Settings → Trusted Publisher:
Field | Value |
Provider | GitHub Actions |
Organization or user |
|
Repository |
|
Workflow filename |
|
Every release after that is a tag:
npm version patch # or minor / major; writes package.json and commits
git push --follow-tagsrelease.yml refuses to publish if the tag and package.json disagree, and asserts
npm >= 11.5.1 before trying — an older client cannot complete the OIDC handshake, silently
falls back to anonymous, and fails with a 404 that reads like a missing package rather than
a missing permission.
Once trusted publishing works, consider turning on "require two-factor authentication and disallow tokens" for the package. It makes this workflow the only way to publish.
License
MIT
This server cannot be installed
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseCqualityDmaintenanceAn MCP server implementation that integrates with Freshdesk, enabling AI models to interact with Freshdesk modules and perform various support operations.Last updated59MIT
- Alicense-qualityDmaintenanceProvides a standard MCP interface for Freshdesk helpdesk operations, including ticket lifecycle management, draft/send replies and notes, contact lookup, and agent/group discovery.Last updatedMIT
- Flicense-qualityBmaintenanceA read-only MCP server that connects AI assistants to Freshservice Service Desk for ticket triage, asset analysis, and support history summarization.Last updated
- AlicenseAqualityCmaintenanceMCP server for the Ticksy support ticket API, enabling AI agents to read and write support tickets.Last updated1626MIT
Related MCP Connectors
Freshdesk MCP Pack — helpdesk ticket and contact management via Freshdesk API v2.
A MCP server built for developers enabling Git based project management with project and personal…
An MCP server that let you interact with Cycloid.io Internal Development Portal and Platform
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/gasconc/freshdesk-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server