MCP Server
The Helmdesk MCP server lets AI assistants (Claude Desktop, Cursor, Windsurf, or any MCP-compatible client) interact with your helpdesk using natural language. Your agent can triage tickets, draft replies, search articles, and send emails — no code required.
Setup
1. Create a scoped API key
Go to your project's API Keys page in the dashboard and create a key with the MCP / AI assistant preset. This grants tickets, articles, feedback, customers, email sending and template management, AI, and log read/write scopes — everything the assistant tools use except webhooks:manage, which you can add explicitly if you want your agent to manage webhook endpoints. See the full scope table below.
2. Add to your MCP client
Claude Desktop
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"helmdesk": {
"command": "npx",
"args": ["-y", "@helmdesk/mcp"],
"env": {
"HELMDESK_API_KEY": "sk_live_your_key_here"
}
}
}
}Cursor / VS Code
Add to your .cursor/mcp.json or VS Code MCP settings:
{
"mcpServers": {
"helmdesk": {
"command": "npx",
"args": ["-y", "@helmdesk/mcp"],
"env": {
"HELMDESK_API_KEY": "sk_live_your_key_here"
}
}
}
}Claude Code (CLI)
claude mcp add helmdesk -- npx -y @helmdesk/mcp
# Then set the env var:
export HELMDESK_API_KEY="sk_live_your_key_here"Codex CLI
Add the server to your ~/.codex/config.toml (note: Codex uses TOML, and the key is mcp_servers with an underscore):
[mcp_servers.helmdesk]
command = "npx"
args = ["-y", "@helmdesk/mcp"]
env = { HELMDESK_API_KEY = "sk_live_your_key_here" }Or add it from the terminal:
codex mcp add helmdesk --env HELMDESK_API_KEY=sk_live_your_key_here -- npx -y @helmdesk/mcp3. Start asking
Once connected, just ask your AI assistant to work with your helpdesk. It will call the right tools automatically.
Example prompts
These are real things you can say. The MCP server translates each into the right API calls.
Triage open tickets
"Show me all open tickets sorted by oldest first. For any waiting more than 24 hours, draft a reply and set them to pending."
list_ticketsget_ticketreply_to_ticketupdate_ticketAnswer with knowledge base
"A customer is asking about file upload limits. Find the relevant KB article and reply with a link."
get_ticketsearch_articlesreply_to_ticketClassify and prioritize
"Go through all new tickets. Anything mentioning 'down', 'broken', or 'outage' should be urgent. Everything else medium."
list_ticketsupdate_ticketSend an email
"Send a welcome email to jane@acme.com using the welcome template with her name."
get_template_schemasend_emailDaily summary
"Summarize my support queue: how many open tickets, what's the oldest, any urgent ones?"
list_ticketsWho is this person?
"jane@acme.com just emailed asking for a call. Look her up — what has she asked us before, how did she rate us, and are there any internal notes?"
list_customersget_customerBulk resolution
"Find all tickets tagged 'known-issue' that are still open, reply with the fix from our KB, and resolve them."
list_ticketsget_ticketsearch_articlesreply_to_ticketupdate_ticketTool reference
The MCP server exposes these tools. Your AI assistant discovers them automatically — you don't need to call them directly.
list_ticketsList support tickets with optional filters.
statusstring?— new, open, pending, resolved, closedprioritystring?— low, medium, high, urgentsearchstring?— Search by subject or emailnumbernumber?— Exact ticket number ("#42") lookuppagenumber?— Page number (default: 1)limitnumber?— Results per page (max: 100)get_ticketGet a ticket with its full message thread, tags, and customer info.
ticketIdstring— The ticket UUIDcreate_ticketCreate a new ticket on behalf of a customer.
subjectstring— Ticket subjectbodystring— Initial message bodycustomerEmailstring— Customer emailcustomerNamestring?— Customer nameprioritystring?— low, medium, high, urgentcategorystring?— bug, feature_request, billing, howto, otherupdate_ticketUpdate a ticket's status, priority, category, or assignee.
ticketIdstring— The ticket UUIDstatusstring?— New statusprioritystring?— New prioritycategorystring?— New categoryassignedTostring?— Member UUID (see list_team_members), or "none" to unassignreply_to_ticketSend a staff reply to a ticket. The reply is emailed to the customer and a new ticket transitions to open.
ticketIdstring— The ticket UUIDbodystring— Reply messageadd_ticket_tagAdd a tag (label) to a ticket for categorization or triage. Returns the full tag list.
ticketIdstring— The ticket UUIDtagstring— Tag to add (1–50 characters)remove_ticket_tagRemove a tag from a ticket. Returns the remaining tags.
ticketIdstring— The ticket UUIDtagstring— Tag to removelist_team_membersList the active members of your account — the valid assignedTo targets for update_ticket.
list_email_templatesList the project's email templates and the account's shared templates. Use this first to discover valid templateKey values.
import_email_templateCreate or update an email template from Handlebars source. Same key = new version; new key = new template. To brand a template, import the layout first and pass its key as layout — then check layoutLinked in the result.
keystring— Template key, e.g. "welcome"bodystring— Handlebars sourcesubjectstring?— Subject line (may contain variables)namestring?— Display nametypestring?— template, layout, or partiallayoutstring?— Key of an existing layout to wrap the template in. Omit to keep the current one; "" removes it.send_emailSend a transactional email using a template.
templateKeystring— Template key (e.g. "welcome")toobject— { email, name? }variablesobject?— Template variablessend_email_batchSend up to 100 transactional emails in one request. Items are delivered independently — a failed item is reported in its result slot without failing the rest. Requires the Pro plan or higher.
emailsarray— Items: { templateKey, to_email, to_name?, variables?, idempotencyKey? }preview_emailPreview a rendered email without sending it.
templateKeystring— Template keyvariablesobject?— Template variablesget_template_schemaGet the variable schema for a template, so you know what variables to pass.
templateKeystring— Template keysearch_articlesSearch the knowledge base for articles matching a query.
querystring?— Search querycategorystring?— Filter by categorycreate_articleCreate a knowledge-base article (defaults to draft; pass status 'published' to make it live).
titlestring— Article titlebodystring?— Markdown bodyslugstring?— URL slug (auto-generated when omitted)excerptstring?— Short summarycategoryIdstring?— Category UUID (see list_article_categories)statusstring?— draft or publishedget_articleGet a single article by ID, including its full markdown body.
articleIdstring— The article UUIDupdate_articleUpdate an article's content, category, or status — including publishing a draft.
articleIdstring— The article UUIDtitlestring?— New titlebodystring?— New markdown bodyslugstring?— New URL slugexcerptstring?— New summarycategoryIdstring?— Category UUID, or "none" to clearstatusstring?— draft or publishedlist_article_categoriesList the project's article categories — the valid categoryId values.
list_webhooksList the project's webhook endpoints (secrets are never included).
create_webhookCreate a webhook endpoint. The signing secret is returned only once, in this response.
urlstring— HTTPS endpoint to deliver events toeventsarray— ticket.created, ticket.resolved, message.received, feedback.submitted, email.bounced, customer.created, customer.updatedslackFormatboolean?— Format payloads for Slackactiveboolean?— Start active (default: true)delete_webhookDelete a webhook endpoint. Deliveries stop immediately.
webhookIdstring— The webhook UUIDwrite_logShip log events to the project (they group into log issues). With a sandbox key, events are quota-exempt.
eventsarray— Items: { level, message, scope?, metadata? }list_log_issuesList grouped error issues from your application logs — the fastest way for an assistant to answer “did anything break?”
statusstring?— open, acknowledged, resolved (comma-separated)levelstring?— Filter by level, e.g. "error,fatal"searchstring?— Match sample message or scopequery_logsQuery raw log events, newest first. Pass a fingerprint from list_log_issues to see every occurrence of one error group.
levelstring?— Filter by level, e.g. "error,fatal"searchstring?— Match message or scopefingerprintstring?— Error-group fingerprintdateFromstring?— ISO date lower bounddateTostring?— ISO date upper boundlist_feedback_conversationsList feedback conversations (customer feedback threads) with optional filters, newest first. Each has a title, status, optional 1–5 rating, source, and customer info.
statusstring?— open or closedratingnumber?— Exact customer rating (1–5)sourcestring?— request, api, widget, dashboardcustomerEmailstring?— Filter by customer emailsearchstring?— Match title or customer email/namedateFromstring?— ISO date lower bounddateTostring?— ISO date upper boundpagenumber?— Page number (default: 1)limitnumber?— Results per page (max: 100)get_feedback_conversationGet a feedback conversation's full detail: the complete message thread (with attachments), rating, and status. Read the whole exchange before replying.
conversationIdstring— The feedback conversation UUIDreply_to_feedbackPost a staff reply to a feedback conversation. The reply is recorded on the thread and emailed to the customer with a link back to the conversation.
conversationIdstring— The feedback conversation UUIDbodystring— Reply message (max 10000 characters)close_feedback_conversationClose a feedback conversation once it has been handled, or pass status 'open' to reopen it. Closing does not notify the customer.
conversationIdstring— The feedback conversation UUIDstatusstring?— Target status (default: "closed"; pass "open" to reopen)list_feedback_requestsList outgoing feedback requests with their funnel status, newest first: sent (emailed) → opened (respond page visited) → responded (conversation created).
statusstring?— sent, opened, respondedcustomerEmailstring?— Filter by customer emailsearchstring?— Match title or customer email/namedateFromstring?— ISO date lower bounddateTostring?— ISO date upper boundpagenumber?— Page number (default: 1)limitnumber?— Results per page (max: 100)request_feedbackAsk a customer for feedback. Creates a feedback request and immediately emails the customer a branded message with a respond link — when they answer, a feedback conversation is created. With a sandbox key the email is captured, not delivered.
titlestring— Short internal title, e.g. "Post-onboarding check-in"messagestring— The message/question the customer receivescustomerEmailstring— Customer email to send the request tocustomerNamestring?— Customer name (used in the email greeting)list_customersList/search the project's customer directory, most recently seen first. The directory builds itself from tickets and feedback — this is everyone who has interacted with the project.
searchstring?— Substring match on email, name, or companyexternalIdstring?— Exact match on YOUR app's user id for the customerpagenumber?— Page number (default: 1)limitnumber?— Results per page (max: 100)get_customerGet a customer with their full interaction history — tickets and feedback conversations, recent-first. The best way for an assistant to answer "who is this person and what have they asked us before?" in one call.
customerIdstring— The customer UUID (find it via list_customers)create_customerCreate a customer explicitly (they're also created automatically from tickets and feedback). Fails with a 409 if the email already exists in this environment.
emailstring— Customer email — unique, immutable after creationnamestring?— Display namecompanystring?— Company nameexternalIdstring?— YOUR app's user id for the customerinternalNotestring?— Staff-only note, never shown to the customermetadataobject?— Arbitrary JSON (max 8 KB serialized)update_customerUpdate a customer's profile. Email is immutable; pass null to clear a field. internalNote is a staff-only, CRM-style note — use it to record context like 'churn risk, complained about pricing in March'.
customerIdstring— The customer UUIDnamestring?— Display name (null clears)companystring?— Company name (null clears)externalIdstring?— YOUR app's user id (null clears)internalNotestring?— Staff-only CRM-style note (null clears)metadataobject?— Arbitrary JSON (null clears)delete_customerRemove a customer from the directory. Their tickets and feedback are untouched (linkage is by email), and a new interaction re-creates the customer.
customerIdstring— The customer UUIDResources
MCP resources provide read-only data snapshots that AI assistants can access directly without calling a tool.
helmdesk://tickets/openAll open tickets — gives the AI instant context about your queue.
helmdesk://tickets/pendingTickets waiting on a customer reply — useful for follow-up workflows.
helmdesk://articlesPublished knowledge base articles — the AI uses these to find answers before drafting replies.
API key scopes
For MCP use, we recommend creating a scoped API key that only grants the permissions your AI assistant needs. The MCP / AI assistant preset in the dashboard is a good starting point.
| Scope | Grants | MCP preset |
|---|---|---|
tickets:read | List and view tickets, list team members | Included |
tickets:write | Create, update, and reply to tickets | Included |
articles:read | Search/read knowledge base + categories | Included |
articles:write | Create and edit articles | Included |
emails:send | Send emails, list templates, preview | Included |
emails:manage | Import/update email templates | Included |
webhooks:manage | List/create/delete webhook endpoints | — |
logs:read | Query log events and grouped log issues | Included |
logs:write | Ingest application log events | Included |
feedback:read | List/view feedback conversations and requests | Included |
feedback:write | Reply, close/reopen, send feedback requests | Included |
customers:read | List/view customers and their interaction history | Included |
customers:write | Create, update, and delete customers | Included |
You can also restrict keys by IP address. See the IP Allowlist section when creating a key.
Troubleshooting
"API key lacks the required scope"
The key you're using doesn't have permission for the operation your AI tried. Go to the API Keys page in your dashboard, click the settings icon on the key, and add the missing scope.
"IP address is not allowed"
The request came from an IP not in the key's allowlist. Either add the IP to the key, or remove the allowlist to allow all IPs.
MCP server not connecting
Make sure you have Node.js 18+ installed and npx available in your PATH. You can test the server manually:
HELMDESK_API_KEY=sk_live_... npx @helmdesk/mcpIf the server starts, the issue is in your MCP client config. Check that the JSON is valid and the env var is set correctly.