# MCP server

# MCP server

Open HelpDesk ships a [Model Context Protocol](https://modelcontextprotocol.io)
server. Point Claude, or any MCP client, at a workspace and it can search
tickets, read a whole conversation, look up what you have already published in
the help centre, and — when you ask it to — answer.

It speaks to the **public REST API**, never to the database. So it inherits the
API key's scopes, its rate limit and its workspace isolation, and it cannot
reach round the rules engine. The same server works against the hosted product
and against a self-hosted instance.

## Setting it up

Two environment variables, and nothing else:

| Variable | Value |
|---|---|
| `OHD_BASE_URL` | `https://acme.open-helpdesk.com` |
| `OHD_API_KEY` | a key from **Settings → API & webhooks** |

### Claude Desktop / Claude Code

```json
{
  "mcpServers": {
    "open-helpdesk": {
      "command": "pnpm",
      "args": ["--filter", "@openhelpdesk/mcp", "run", "start"],
      "cwd": "/path/to/open-helpdesk",
      "env": {
        "OHD_BASE_URL": "https://acme.open-helpdesk.com",
        "OHD_API_KEY": "ohd_live_…"
      }
    }
  }
}
```

Run from a clone for now — the package is not published to npm yet, and this
page will show the one-line `npx` form when it is, not before.

Give it a **read-only key** unless you actually want the assistant writing to
your helpdesk. Scope is chosen when you mint the key — read, read/write, or
create-tickets-only — and a read key turns every mistake into a `403`.

## The tools

Nine, chosen for what someone does with a helpdesk rather than one per
endpoint — a faithful mirror of thirty-two routes is thirty-two choices an
assistant makes badly.

### Reading

| Tool | What it does |
|---|---|
| `search_tickets` | Find tickets by status, priority, assignee, organization, tag, or what changed since a given time. |
| `get_ticket` | One ticket and its whole thread, internal notes included. |
| `search_knowledge_base` | Published articles matching some words. |
| `get_article` | One article in full. |
| `find_contact` | Look someone up by email. |
| `list_workspace` | Agents, teams, organizations or tags — for assigning and filing. |

### Writing

| Tool | What it does |
|---|---|
| `create_ticket` | Opens a ticket. **Reaches a real customer**: rules, SLA policies and acknowledgement emails run as they would for an inbound email. |
| `reply_to_ticket` | Adds a message. Defaults to an **internal note**; `internal: false` sends it to the customer. |
| `update_ticket` | Status, priority, assignee, tags, type, organization. Sends nothing itself — but the workspace's rules may. |

The writing tools are annotated as such, so a client that asks for confirmation
knows which ones to ask about. `reply_to_ticket` defaults to an internal note on
purpose: drafting is the common case, and sending to a customer should be the
sentence you typed, not the default you forgot.

## What it will not do

- **It cannot configure the workspace.** Rules, SLA policies, macros and views
  are readable so an assistant can reason about them, and are changed by a human
  who can see the consequences.
- **It cannot delete anything.** No tool maps to `DELETE`.
- **It cannot see another workspace.** The key carries one, and only one.

## Using it well

The pattern that works:

> Read ticket 4821, check the knowledge base for what we have already published
> on that, and draft a reply as an internal note.

The assistant reads the thread, finds the article, drafts — and you send it, or
you do not. Asking it to reply publicly straight away skips the one step where a
mistake is still cheap.

## Running it from source

```bash
git clone https://github.com/Open-HelpDesk/open-helpdesk
pnpm install
OHD_BASE_URL=https://acme.open-helpdesk.com OHD_API_KEY=ohd_live_… \
  pnpm --filter @openhelpdesk/mcp run start
```

The server is `packages/mcp`, AGPL-3.0 like the rest of the core. It logs to
stderr only — stdout is the protocol.
