MCP Server
Let claude.ai, ChatGPT, Claude Code or any MCP client work your queue — sign in once and choose the workspaces it may use.
Overview
PimiDesk serves a Model Context Protocol server over streamable HTTP. An MCP client — claude.ai, ChatGPT, Claude Code, an IDE, your own agent — signs in with OAuth or connects with your API token and gets 40 tools shaped around support work: read a ticket as plain text, find what needs attention, draft or send a reply, file a ticket from a Slack thread.
The tools are deliberately not one-per-REST-endpoint. Each returns agent-ready data (plain text, quoted history stripped, ids you can act on) and refuses anything ambiguous instead of guessing. The REST API is still there for everything else.
Endpoint
One endpoint for every workspace you use:
https://desk-mcp.pimi.io/mcp Every tool except list_workspaces and manage_account takes a workspace_id, so one connector covers all the workspaces you allow. The agent calls list_workspaces first to learn them; when a connection can use exactly one workspace, workspace_id may be left out. Every workspace rule — membership, the workspaces you ticked, plan entitlement, channel settings — is checked on every call, exactly as the REST API checks it.
Only POST is served. Requests must carry Accept: application/json, text/event-stream, which every MCP client sends.
Sign In with OAuth
claude.ai, ChatGPT and Claude Code sign in with OAuth — no token to copy. Add https://desk-mcp.pimi.io/mcp as a custom connector and your browser opens PimiDesk's Allow access page. There you choose two things:
- What it may do: Read only; Drafts only — save draft replies, add notes and change a ticket's tags, priority or assignee, but never send; or Read and write — also reply to customers and update tickets as you.
- Which workspaces: every active workspace you belong to is ticked; untick any the connection should not see. Workspaces you join later are not added automatically — connect again to add one.
Your API Tokens page lists each connected app with its workspaces; Disconnect revokes it. Leaving a workspace removes it from every connection.
Use an API Token Instead
A client that can send a header may skip sign-in and use a personal access token as a Bearer header — the same token that drives the REST API. A token reaches every workspace you belong to. Create one from your API Tokens page (in the user menu), or over the API:
curl -X POST \
-H "Authorization: Bearer YOUR_LOGIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "name": "Claude", "abilities": ["read", "write"] }' \
https://desk-api.pimi.io/api/v1/tokens Abilities matter here. A token minted with ["read"] can call every read tool and is refused, in-band, by everything else — the safe choice for an agent you have not fully trusted yet. ["draft"] adds drafts, notes and ticket tags, priority and assignee, but never sends. ["read", "write"] (or the default *) unlocks replies, new emails, new tickets and status changes.
Connect a Client
claude.ai and ChatGPT
Add a custom connector with the URL https://desk-mcp.pimi.io/mcp and sign in when asked.
Claude Code
claude mcp add --transport http pimidesk https://desk-mcp.pimi.io/mcp Claude Code opens the same sign-in page. To use a token instead:
claude mcp add --transport http pimidesk \
https://desk-mcp.pimi.io/mcp \
--header "Authorization: Bearer YOUR_TOKEN" Or in a .mcp.json / settings file, any client that reads the standard shape:
{
"mcpServers": {
"pimidesk": {
"type": "http",
"url": "https://desk-mcp.pimi.io/mcp",
"headers": {
"Authorization": "Bearer YOUR_TOKEN"
}
}
}
} Other clients
Any MCP client that supports streamable HTTP with custom headers (Cursor, Windsurf, the official SDKs) takes the same URL and header. A client that only speaks stdio, or cannot set headers, can be bridged with mcp-remote, passing the header on its command line.
Any JSON-RPC client
A raw tools/list call, to check the connection from a terminal:
curl -X POST \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Accept: application/json, text/event-stream" \
-H "Content-Type: application/json" \
-d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }' \
https://desk-mcp.pimi.io/mcp Tools
Reads work on any connection. draft tools need drafts or write access; write tools need write access. A read / write tool reads on any connection and needs write access for anything that changes data.
| Tool | Access | What it does |
|---|---|---|
list_workspaces | read | Who the connection acts as and the workspaces it can use. Call it first: the workspace tools take a workspace_id from here. |
list_inbox | read | What needs attention: tickets, the shared inbox or your personal inbox, with status / priority / assignee / tag filters and cursor paging. |
list_drafts | read | Unsent drafts, yours by default or your team's too, with a flag when the customer wrote again after the draft was saved. |
list_members | read | The workspace members with the ids that update_ticket and create_ticket take as an assignee. |
list_tags | read | The tags that already exist, with usage counts — so a reply reuses a tag instead of inventing a near-duplicate. |
search | read | Free-text search over tickets, conversations, contacts or organizations; body matches say which message matched. |
get_ticket_context | read | A ticket as plain text: status, contact, other open tickets from the same contact, and the thread with quoted history stripped. |
get_conversation_context | read | A shared-inbox or personal-inbox conversation as plain text, new content only. |
get_attachment | read | One attached file: text as text, images as an image (a resized copy when the original is large), anything else as a 24-hour download link. |
save_draft | draft | Save a draft reply for a human to review and send from the dashboard. Never customer-visible. |
add_note | draft | A private internal note on a ticket or conversation. Never emailed to the customer; teammates it mentions are told by email. |
update_ticket | draft | Change tags, priority or assignee (draft access), or status too (write access). Everything is checked before anything is written. |
manage_summary | read / write | Read a thread's short internal summary on any connection; writing or deleting it needs write access. |
create_ticket | write | Open a ticket for an existing or inline contact. Emails nobody unless send_confirmation is true. |
reply_to_ticket | write | Email a reply to the customer on a ticket. Customer-visible — the agent confirms the text with you first. |
reply_to_conversation | write | Email a reply on a shared-inbox or personal-inbox conversation, from that inbox's own address. |
compose | write | Start a new email thread from the shared inbox or your personal inbox. Sends a real email — the agent confirms recipient, subject and text with you first. |
get_draft | read | Your own unsent draft on one thread, with its recipients, files and whether the customer wrote again since. |
delete_draft | draft | Discard your draft on a thread. A workspace owner with write access can discard a teammate's by id. |
update_conversation | draft | The same as update_ticket for a shared-inbox or personal-inbox conversation: tags, assignee and GitHub links (draft access), or status, read state and contact too (write access). |
triage | write | Archive, mark as spam, trash or restore a ticket or conversation, one or up to 50 at a time. Marking as spam also blocks the sender. Permanent deletes need a workspace owner, and the agent confirms first. |
manage_message | read / write | One message in a thread: trash, restore, permanently delete, list trashed, or rebuild a garbled body from the stored original. |
resend | write | Send a ticket reply, or the ticket confirmation, again. Emails the customer — the agent confirms first, and a retry with the same key never sends twice. |
get_raw_message | read | The original email as it arrived: headers by default, or one body part. For "how did this get here". Originals are kept for about 90 days. |
get_dashboard_link | read | The dashboard URL for a ticket, conversation, contact, organization, tag, member or settings page — for handing a thread to a human. |
get_contact | read | One contact in full with custom fields, lists and recent threads, or a filtered list of contacts. |
manage_contact | write | Create, update or delete a contact, including its status, organization and custom field values. |
manage_contact_field | read / write | Contact custom field definitions. Anyone can read them; changes need a workspace owner. |
manage_contact_list | read / write | Contact lists and their members. Anyone can read them; changes need a workspace owner. |
manage_organization | read / write | List, show, create, update or delete organizations, with their custom field values. |
manage_organization_field | read / write | Organization custom field definitions. Anyone can read them; changes need a workspace owner. |
manage_tag | write | Create, rename, recolour or delete a tag. Deleting one takes it off every thread. |
list_canned_responses | read | The team's saved replies: a list with previews, or one with its full text. |
manage_canned_response | write | Create, edit or delete a saved reply. |
manage_blocked_sender | read / write | List blocked senders and the heaviest recent senders; block or unblock an address. Domain and pattern rules need a workspace owner. |
manage_automation | read / write | Read automation rules on any connection. Creating, changing or dry-running a rule needs a workspace owner and write access. |
workspace_overview | read | Workspace settings, plan usage, queue totals, your own queue, sending addresses and (for owners) domains with their DNS records. |
workspace_stats | read | How the team is doing over 7, 30 or 90 days: new and closed tickets, replies, response and resolution times, SLA, backlog. |
manage_member | read / write | Show a member or the pending invitations; a workspace owner can invite, change a role or remove someone. Invitations send an email — the agent confirms first. |
manage_account | read / write | Your API tokens and what each may do; revoke one, or create a workspace (with an API token, not a signed-in connector). It never creates a token. |
Every tool refuses an argument it does not declare (with a "did you mean" for an obvious misspelling) and every enum value it does not know. That is deliberate: a tool that silently falls back to a default hands an AI client a confidently wrong answer.
Conduct Rules
The server ships its rules of conduct inside the tool descriptions, so every client inherits them without a system prompt:
- Plain text only. Replies, notes and drafts carry no HTML. Quoted history is added to outgoing mail server-side, so the agent sends only the new text.
- Confirm before anything customer-visible.
reply_to_ticket,reply_to_conversationandcreate_ticketwith a confirmation email tell the agent to show you the exact text first.save_draftis the safe path: the draft lands in the dashboard reply box for a human to send. - Confirm before anything permanent. Deleting a contact, a tag or a message, emptying spam, removing a member, revoking a token and switching on an automation that replies or trashes all tell the agent to check with you first.
- Tickets are not conversations. A shared-inbox or personal-inbox thread is a conversation with its own tools and its own sending address; a ticket is a ticket.
- Ids are not guesses. Assignees come from
list_members, tags fromlist_tags, attachments from the thread'sAttachments:line.
Trial workspaces are reply-only over MCP too. While a workspace is on trial, create_ticket is refused and a reply can only reach an address already on the thread — the same rule the dashboard and the REST API apply.
Limits
- Every plan includes the MCP server. Calls are ordinary API calls and count against your existing limits; nothing is metered separately.
- Rate limit: 120 requests per minute per user, on the MCP endpoint's own bucket — an agent in a loop cannot starve your dashboard, and vice versa.
- Quotas apply where they apply to the REST API: a new tag name on
update_ticketcounts against your tag limit and is refused up front, before anything is written. - Thread rendering returns the newest 40 messages, capped at 4,000 characters each, and leaves automatic replies out; the response says how many were omitted.
- Attachments are read up to 5 MB. Sending files works by uploading over REST first (
POST /api/v1/workspaces/{workspace_id}/attachments) and passing the ids asattachment_ids.
Frequently Asked Questions
Which plans include the MCP server?
Every plan. MCP calls are ordinary API calls under your existing limits; there is no separate metering.
Can an agent send email to my customers?
Only through reply_to_ticket, reply_to_conversation, compose and create_ticket with send_confirmation, and the tool descriptions tell the agent to confirm the exact text with you first. A read-only or drafts-only connection cannot send anything. save_draft never emails anyone.
Can one connector reach several workspaces?
Yes. Sign in once and tick the workspaces the connection may use; every tool takes a workspace_id, and list_workspaces shows the ones it can use. Workspaces you join later are not added automatically — connect again to add one. A personal access token reaches every workspace you belong to.
Does the MCP server support OAuth sign-in?
Yes. claude.ai, ChatGPT and Claude Code connect with OAuth: add the URL, sign in to PimiDesk, choose the access level and the workspaces. Clients that can send a header may use a personal access token as a Bearer header instead.
Related
- Getting Started — Creating your first API token
- Webhooks — Push events to your own agent instead of polling
- API Reference — The full REST surface the tools sit on top of