Skip to content
On this page

AI and MCP

MCP server

Connect Claude, ChatGPT, Cursor, VS Code or any Model Context Protocol client to your helpdesk. Search, read, reply to and route tickets in natural language, with scoped access that acts with your own permissions. Sign in with OAuth or use a token.

Endpoint: https://zono.support/mcp

Endpoint

The server speaks the MCP streamable HTTP transport at a single URL. Clients that support OAuth need nothing else; others send a token in the Authorization header.

MCP endpoint
https://zono.support/mcp

Connect with OAuth

The recommended way. Your client registers itself, you sign in to Zono in the browser, pick the workspace and the scopes to grant, and approve. No token to copy or rotate.

  • Claude and ChatGPT: add a custom connector (in Claude: Settings > Connectors; in ChatGPT: Settings > Connectors) and paste https://zono.support/mcp. Connect, sign in, approve.
  • Cursor and VS Code: configure only the URL (see Client setup). The consent page opens the first time the client connects.
  • Claude Code: add the server by URL, then run /mcp and choose Authenticate.

The consent page names the app as unverified (it names itself when it registers) and shows where it will send you back, so only approve apps you are connecting yourself. Clients must use PKCE with S256, and redirect URIs must match the registered ones exactly. Discovery documents are at /.well-known/oauth-protected-resource and /.well-known/oauth-authorization-server.

Bearer tokens

For clients without OAuth, scripts and CI, send a personal access token: Authorization: Bearer <token>. Tokens are created under Account > API & MCP tokens, are scoped to one workspace, carry the scopes you tick, and are shown once. A token expires after 90 days unless you pick another lifetime (7, 30 or 365 days, or never), and we email you each time one is created. You can also give it an IP allowlist; requests from other addresses are refused. The same token also works for the REST API.

New to Zono? Start free, then create a token.

Scopes

Every tool needs one scope. OAuth connections carry the scopes approved on the consent page; tokens carry the ones ticked when they were created (the Read only and Read and write presets tick the read scopes, or all of them). Your client still lists every tool: a call outside the token's scopes returns an error result naming the missing scope, and nothing changes. Tokens made before scopes existed keep working as before: read-only tokens can call the read tools, read-write tokens every tool.

ScopeAllowsTools
tickets:readList, search and read tickets, their replies, notes and custom fields.list-tickets, get-ticket, search-tickets, list-ticket-fields
tickets:writeCreate tickets, reply, add notes, assign, change status or fields, and merge tickets.reply-to-ticket, assign-ticket, update-ticket-status, update-ticket-fields, create-ticket, merge-tickets
customers:readList and read customers (team owners and admins only).list-customers
knowledge:readSearch the help center and knowledge sources, and ask for cited answers.search-knowledge, ask-knowledge
licenses:readLook up and verify license keys and purchases.lookup-license

REST API only, no MCP tool (OAuth apps that ask for them, such as automation tools, are offered them): customers:write (change customers), conversations:read (read conversations), conversations:write (change conversations), webhooks:manage (manage webhooks).

Lifetimes and revocation

OAuth access tokens last 60 minutes and clients renew them with a refresh token, which is replaced on every use and expires after 180 days unused. Authorization codes last 10 minutes and work once. Disconnect an app under Account > API & MCP tokens > Connected apps (team owners and admins can also do it from Settings > API tokens): its access and refresh tokens stop working at once. Leaving a workspace cuts off access too. Personal access tokens last until they expire or are revoked.

Tools

Your client discovers these automatically. Parameters marked * are required.

list-ticketsReadtickets:read

List tickets in the current team that the agent can access, optionally filtered by status, assignee, priority, department or custom field values.

ParameterTypeDescription
statusstringFilter by ticket status: open, answered, on-hold, or closed.
assignee_idstringFilter to tickets assigned to a specific user ULID.
department_idstringFilter to tickets in a specific department ULID.
prioritystringFilter by priority: low, normal, high, or urgent.
limitintegerMaximum number of tickets to return (default 25, max 100).
custom_fieldsobjectFilter by custom field values keyed by field key: a value (or list of option values) to match, {"min","max"} for numbers, {"from","to"} for dates, "__empty__" for no value or "__any__" for any value.
get-ticketReadtickets:read

Fetch the full conversation thread for a single ticket by ID or number, including all replies, internal notes, SLA status and custom field values.

ParameterTypeDescription
ticket*stringTicket ULID or ticket number.
reply-to-ticketWritetickets:write

Post a reply to a ticket as the authenticated agent. Set is_internal=true for staff-only notes.

ParameterTypeDescription
ticket*stringTicket ULID or ticket number to reply to.
message*stringReply body (plain text or HTML). HTML is preserved.
is_internalbooleanWhen true, the reply becomes a staff-only internal note (default: false).
close_ticketbooleanWhen true, close the ticket after posting the reply (default: false).
assign-ticketWritetickets:write

Assign a ticket to a team member, or unassign it by passing assignee_id=null.

ParameterTypeDescription
ticket*stringTicket ULID or ticket number.
assignee_idstringUser ULID to assign the ticket to. Omit or send null to unassign.
update-ticket-statusWritetickets:write

Change a ticket status (open, answered, on-hold, closed). Closing needs every required custom field filled in; pass missing values in custom_fields, keyed by field key.

ParameterTypeDescription
ticket*stringTicket ULID or ticket number.
status*stringNew ticket status: open, answered, on-hold, or closed.
custom_fieldsobjectOptional custom field values to save with the status change, keyed by field key, e.g. {"order_number": "1001"}. Use list-ticket-fields for keys, types and options.
update-ticket-fieldsWritetickets:write

Set custom field values on an existing ticket. Values are keyed by field key (see list-ticket-fields); null or an empty value clears an optional field.

ParameterTypeDescription
ticket*stringTicket ULID or ticket number.
custom_fields*objectCustom field values keyed by field key, e.g. {"order_number": "1001", "category": "bug"}. Multi-select values are arrays of option values, dates are YYYY-MM-DD. Only fields that apply to the ticket's department and product are accepted.
create-ticketWritetickets:write

Create a new ticket on behalf of a customer. The customer and product must already exist in the team. Custom field values are keyed by field key (see list-ticket-fields).

ParameterTypeDescription
customer_id*stringCustomer ULID the ticket is created for.
subject*stringTicket subject line.
description*stringInitial ticket body / description.
product_id*integerProduct ID the ticket relates to.
prioritystringOptional priority: low, normal, high, urgent.
department_idstringOptional department ULID to route the ticket to.
custom_fieldsobjectOptional custom field values keyed by field key, e.g. {"order_number": "1001", "category": "bug"}. Use list-ticket-fields for keys, types and options; multi-select values are arrays of option values, dates are YYYY-MM-DD.
search-ticketsReadtickets:read

Full-text search across ticket subject, description, and customer name. Honors per-agent access rules.

ParameterTypeDescription
query*stringSearch term to match against subject, description, customer name, and customer email.
statusstringOptional status filter.
limitintegerMaximum results to return (default 20, max 50).
list-customersReadcustomers:read

List or search customers belonging to the current team. Team admins and owners only, as in the dashboard.

ParameterTypeDescription
searchstringOptional substring to match against customer name or email (case-insensitive; % and _ match literally).
limitintegerMaximum customers to return (default 25, max 100).
search-knowledgeReadknowledge:read

Search the team knowledge (help articles, saved replies, snippets, resolved tickets, websites, files) with the team's AI Knowledge settings. Returns ranked passages with scores and citations.

ParameterTypeDescription
query*stringWhat to look up.
ticketstringOptional ticket ID or number to search in the context of (its product, department and channel).
product_idstringOptional product ID to scope the search to.
limitintegerMaximum passages to return (1-20, default from settings).
ask-knowledgeReadknowledge:read

Ask a question and get an answer grounded in the team knowledge, with [S#] citations, a confidence score and whether a human should handle it.

ParameterTypeDescription
query*stringThe question to answer.
ticketstringOptional ticket ID or number to answer in the context of.
product_idstringOptional product ID to scope the answer to.
list-ticket-fieldsReadtickets:read

List the team's active custom ticket fields with their keys, types, options and whether they are required, for use with create-ticket and list-tickets.

No parameters.

merge-ticketsWritetickets:write

Merge a duplicate ticket (source) into another ticket (target) of the same customer. The target receives the source's replies, notes, attachments and tags; the source is closed and linked to the target, and future customer messages to it land on the target. Tickets of different customers cannot be merged.

ParameterTypeDescription
source*stringTicket ULID or number of the duplicate ticket to merge (it will be closed).
target*stringTicket ULID or number of the ticket to merge into (it stays open). Must belong to the same customer as the source.
notify_customerbooleanTell the source ticket's customer where their request went. Defaults to false.
notestringOptional internal note added to both tickets.
lookup-licenseReadlicenses:read

Look up licenses and purchases: pass a ticket (ID or number) for its requester's storefront card (stored licenses, purchases found by email and a refund flag), or a license_key (team admins and owners only) for the stored license with that key and its customer. Keys come back masked to their last four characters.

ParameterTypeDescription
ticketstringTicket ULID or ticket number: returns its requester's licenses and purchases. Pass this or license_key, not both.
license_keystringA license key or purchase code to find among the team's stored licenses (team admins and owners only). Pass this or ticket, not both.

Permission model

Tools run as the user who connected the app or issued the token, scoped to one workspace. They inherit that user's role and department and product access, so the AI sees and changes only the tickets you could in the dashboard; scopes narrow that further, never widen it. Customer listing is limited to team owners and admins, as in the dashboard. Leaving the workspace, disconnecting the app or revoking the token cuts off access immediately.

Rate limits

60 requests per minute per token (or per connected app) and 300 per workspace; the tighter limit applies. The per-workspace limit stops many tokens on one workspace from crowding out others. Over the limit, requests get a 429 with Retry-After.

Audit trail

Every tool call is logged after it runs, with the tool, the user, the token or connected app, its outcome (including calls refused for a missing scope) and its latency. Creating and revoking tokens, and connecting and disconnecting apps, are logged too. Team owners see these entries in the workspace activity log.

Client setup

OAuth first; with a bearer token, replace <YOUR_TOKEN> with your token.

Claude Code

Run this once in your terminal, then type /mcp in Claude Code and choose Authenticate: your browser opens the Zono sign-in and consent page. With a token instead, add the Authorization header.

claude mcp add --transport http zono-support https://zono.support/mcp

Claude Desktop

Easiest: Settings > Connectors > Add custom connector, then paste the endpoint. To configure it by file instead, Claude Desktop starts local servers from claude_desktop_config.json, so it reaches Zono through the mcp-remote bridge (needs Node.js for npx); mcp-remote opens the consent page itself. The bearer variant passes the header through an env var because some clients do not escape spaces in args.

{
    "mcpServers": {
        "zono-support": {
            "command": "npx",
            "args": [
                "-y",
                "mcp-remote",
                "https://zono.support/mcp"
            ]
        }
    }
}

Cursor

Add the server to ~/.cursor/mcp.json (or .cursor/mcp.json in a project) with only the URL. The first time it connects, Cursor asks you to log in and opens the consent page. With a token instead, add the headers block.

{
    "mcpServers": {
        "zono-support": {
            "url": "https://zono.support/mcp"
        }
    }
}

VS Code

Add the server to .vscode/mcp.json (or your user settings) with only the URL. VS Code asks you to sign in when it first starts the server.

.vscode/mcp.json
{
    "servers": {
        "zono-support": {
            "type": "http",
            "url": "https://zono.support/mcp"
        }
    }
}