Skip to main content
Glama
roddyst

i-net HelpDesk MCP Server

by roddyst
README.md
# i-net HelpDesk MCP Server

Ein [MCP](https://modelcontextprotocol.io)-Server, der die **Ticket-Web-API des
i-net HelpDesk** als Werkzeuge für beliebige KI-Agenten bereitstellt: Tickets
suchen und lesen, Bearbeitungsschritte ansehen, neue Tickets anlegen und
Ticketaktionen (antworten, schließen, eskalieren …) ausführen — inklusive
Dateianhängen.

Der Server kann auf zwei Arten betrieben werden:

| Modus | Wofür | Authentifizierung |
| --- | --- | --- |
| **stdio** | lokaler Prozess je Agent (Claude Desktop/Code, Cursor, VS Code …) | Token bzw. Benutzer/Passwort aus Umgebungsvariablen |
| **HTTP** (streamable) | zentral gehostet, mehrere Nutzer teilen sich einen Serverprozess | jeder Client schickt seinen eigenen `Authorization`-Header mit, optional zusätzlich die HelpDesk-URL |

---

## Voraussetzungen

* Python 3.10 oder neuer
* Ein i-net HelpDesk mit aktivierter Web-API
* Ein Benutzer mit dem Recht **„Web API"** — ohne dieses Recht antwortet der
  Server mit HTTP 403. Welche Tickets sichtbar sind und welche Aktionen
  erlaubt sind, richtet sich nach den Rollen dieses Benutzers.

## Installation

```bash
# direkt aus dem Repository ausführen (empfohlen für den Einstieg)
uvx --from git+https://github.com/roddyst/i-net_mcp_server inet-helpdesk-mcp --help

# oder klassisch installieren
pip install git+https://github.com/roddyst/i-net_mcp_server
```

Für die Entwicklung:

```bash
git clone https://github.com/roddyst/i-net_mcp_server
cd i-net_mcp_server
python -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest
```

---

## Schnellstart: stdio (lokaler Agent)

```bash
export INET_BASE_URL="https://helpdesk.example.com:9000"
export INET_TOKEN="VGhpcyBpcyBqdXN0IGEgZGVtbyBhY2Nlc3MgdG9rZW4u"
inet-helpdesk-mcp
```

Konfiguration für Claude Desktop / Claude Code (`claude_desktop_config.json`
bzw. `.mcp.json`) — weitere Beispiele liegen unter [`examples/`](examples/):

```json
{
  "mcpServers": {
    "i-net-helpdesk": {
      "command": "uvx",
      "args": ["--from", "git+https://github.com/roddyst/i-net_mcp_server", "inet-helpdesk-mcp"],
      "env": {
        "INET_BASE_URL": "https://helpdesk.example.com:9000",
        "INET_TOKEN": "dein-access-token"
      }
    }
  }
}
```

Statt eines Tokens gehen auch `INET_USERNAME` und `INET_PASSWORD` (Basic Auth).
Der Token wird als `Authorization: Bearer <token>` gesendet, genau wie in der
i-net-Dokumentation beschrieben.

## Schnellstart: HTTP (zentral gehostet)

```bash
inet-helpdesk-mcp --transport http --host 0.0.0.0 --port 8000 \
                  --base-url https://helpdesk.example.com:9000
```

Der Endpunkt liegt dann unter `http://<host>:8000/mcp`. Der Agent trägt diese
URL ein und schickt seinen HelpDesk-Token im `Authorization`-Header mit — genau
das ist der Ablauf „URL + Bearer-Token", der Server reicht den Header an den
HelpDesk weiter. Beispiel für einen MCP-Client, der Remote-Server unterstützt:

```json
{
  "mcpServers": {
    "i-net-helpdesk": {
      "type": "http",
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer dein-access-token" }
    }
  }
}
```

**Ohne** `--base-url` bestimmt der Client zusätzlich das Zielsystem über den
Header `X-Inet-Base-Url`. Das ist praktisch für Mandanten mit mehreren
HelpDesk-Instanzen, öffnet den Server aber als Proxy für beliebige Adressen —
in einem offenen Netz deshalb besser eine feste `--base-url` setzen (dann ist
der Header abgeschaltet, außer er wird mit `--allow-url-header` erlaubt).

> **Hinweis zum Betrieb:** Der Server terminiert selbst kein TLS und
> authentifiziert Clients nicht eigenständig — die Anmeldung passiert am
> HelpDesk mit dem durchgereichten Token. Wenn er über das lokale Netz hinaus
> erreichbar sein soll, gehört ein Reverse Proxy mit HTTPS davor.

### Variante: fester Service-Account statt Token pro Nutzer

Läuft der Server auf derselben Maschine wie der Agent, ist oft ein einziger
technischer Benutzer gewollt. Dann liegt der Token auf dem Server und
`--ignore-client-auth` sorgt dafür, dass ein `Authorization`-Header des Agenten
ihn **nicht** überschreibt (Agenten schicken solche Header gelegentlich für
eigene Zwecke mit — ohne den Schalter landet der beim HelpDesk und jeder Aufruf
scheitert mit 401):

```bash
INET_BASE_URL=https://helpdesk.example.com:9000 INET_TOKEN=… \
  inet-helpdesk-mcp --transport http --host 127.0.0.1 --port 8765 --ignore-client-auth
```

Fertige systemd-Unit, Env-File und Schritt-für-Schritt-Anleitung dafür:
[`deploy/`](deploy/README.md).

---

## Werkzeuge

| Tool | Web-API | Beschreibung |
| --- | --- | --- |
| `server_info` | – | Zeigt die Konfiguration und prüft Verbindung + Zugangsdaten. Erste Anlaufstelle bei Fehlern. |
| `search_tickets` | `POST /api/ticket/search` | Tickets über eine Suchphrase finden (`query`, `limit`, `start`, `locale`); mit `include_details` gleich samt Betreff und Status. |
| `get_ticket` | `GET /api/ticket/<id>` | Felder und Attribute eines Tickets; `fields` grenzt die Antwort ein. |
| `get_ticket_conversation` | mehrere | Ticket **und** Bearbeitungsschritte inklusive Texte in einem Aufruf — der schnellste Weg, ein Ticket zu verstehen. |
| `list_ticket_actions` | `GET /api/ticket/<id>/actions` | Aktuell erlaubte Ticketaktionen als Map „Id → Anzeigename". |
| `list_ticket_steps` | `GET /api/ticket/<id>/steps` | Bearbeitungsschritte eines Tickets ohne Texte, optional ab Zeitstempel `since`. |
| `get_ticket_step` | `GET /api/ticket/<id>/steps/<step-id>` | Ein Bearbeitungsschritt inklusive Text. |
| `create_ticket` | `POST /api/ticket/create` | Neues Ticket anlegen, liefert die Ticket-Id. |
| `apply_ticket_action` | `POST /api/ticket/<id>/apply` | Ticketaktion ausführen, liefert die Id des neuen Bearbeitungsschritts. |

`create_ticket` und `apply_ticket_action` werden mit `--read-only` gar nicht
erst registriert — sinnvoll, wenn ein Agent nur lesen können soll.

Ticket-Ids werden sowohl als Zahl als auch in der kodierten Form akzeptiert,
die in den Betreffzeilen der HelpDesk-E-Mails steht.

### Typischer Ablauf

1. `search_tickets` mit einer Phrase wie `Drucker` oder `Resource:"First Level Support"`
2. `get_ticket_conversation` zum Lesen — Ticket und Verlauf in einem Aufruf;
   `get_ticket`, `list_ticket_steps` und `get_ticket_step` sind die feinkörnigen Varianten
3. `list_ticket_actions`, um die gültige `action_id` zu ermitteln
4. `apply_ticket_action` mit dieser Id — die Ids unterscheiden sich je Ticket,
   Benutzer und Ticketstatus, sie dürfen also nicht geraten werden.

### Aufbereitete Antworten

Die Web-API antwortet so, wie ihre eigene Oberfläche es braucht: Kennzahlen statt
Beschriftungen, Zeitstempel in Millisekunden, Schritttexte als rohes HTML. Für ein
Sprachmodell kostet das Tokens und Genauigkeit, deshalb werden die Antworten
aufbereitet:

* `statusid: 400` plus `statusid_display: "Geschlossen"` wird zu
  `statusid: { "value": 400, "display": "Geschlossen" }`
* neben einem Zeitstempel steht sein ISO-Zwilling, z. B.
  `closeddate_iso: "2020-09-28T12:05:09Z"` — der Originalwert bleibt erhalten
* Schritttexte mit `htmlContent: true` kommen als lesbarer Text an (`textWasHtml: true`);
  `max_text_chars` kürzt sehr lange Texte mit einem sichtbaren Hinweis

Wer die Rohantwort braucht, setzt am einzelnen Aufruf `raw: true` oder startet den
Server mit `--no-normalize`.

### Leitplanken für schreibende Tools

`--read-only` ist ganz oder gar nicht. Dazwischen liegen drei Schalter:

```bash
# Der Agent darf antworten (-9), aber nicht reaktivieren (-2), und nie Mails auslösen
inet-helpdesk-mcp --allowed-actions=-9 --default-automail NEVER

# Alles prüfen, nichts senden: liefert die Anfrage zurück, die gestellt worden wäre
inet-helpdesk-mcp --dry-run
```

* `--allowed-actions` / `--denied-actions` brauchen das Gleichheitszeichen
  (`--allowed-actions=-9,-12`), weil Aktions-Ids negativ sind und getrennt
  geschrieben als Option gelesen würden. Sie werden **vor** dem Aufruf geprüft; gesperrte
  Aktionen erscheinen auch in `list_ticket_actions` nicht mehr (vermerkt als
  `hiddenByPolicy`).
* `--default-automail` ergänzt `ticketextension.automail`, wenn der Aufruf selbst keinen
  Wert setzt. Ein Tippfehler beendet den Serverstart, statt beim HelpDesk stillschweigend
  verworfen zu werden — und damit die Mails auszulösen, die er verhindern sollte.
* `--dry-run` validiert Aktionspolitik, Anhänge und Payload und antwortet mit
  `{"dryRun": true, "method", "path", "payload", "attachments"}`.

### Ticketfelder und Aktionsargumente

`ticket_fields`, `step_fields` und `action_arguments` sind optional und werden
im Normalfall nicht gebraucht. Wenn doch, gelten die Regeln der Web-API:
Schlüssel müssen echten Feldschlüsseln (oder deren lokalisiertem Anzeigenamen)
entsprechen, Werte sind Strings; JSON-Werte müssen als String kodiert werden.
Beispiele aus der i-net-Dokumentation:

```jsonc
{
  "ticketextension.dispatchNow": "ALWAYS",           // Ticket sofort disponieren
  "ticketextension.automail": "NO_MAILS_TO_ENDUSER", // keine Auto-Mails an Endanwender
  "processingtimeextension.appointment": "1733875200000", // Wiedervorlage/Termin
  "ticketactionextension.escalate": "{'targetResID':'<GUID>','changeTicketStatus':true}"
}
```

Unbekannte **Ticketfelder** führen zu einem Fehler, unbekannte
**Aktionsargumente** werden vom HelpDesk stillschweigend verworfen und nur ins
Debug-Log geschrieben.

### Anhänge

Anhänge werden als Liste übergeben, jeweils mit Inhalt **entweder** inline als
Base64 **oder** als Pfad auf dem Dateisystem des Servers:

```jsonc
{
  "text": "Anfrage mit Anhang",
  "attachments": [
    { "name": "screenshot.png", "content_base64": "iVBORw0KGgo…" },
    { "path": "/tmp/protokoll.pdf", "attachment_type": "Attachment" }
  ]
}
```

`path` funktioniert nur im stdio-Modus, in dem Agent und Server dieselbe
Maschine teilen; in den HTTP-Modi ist es automatisch abgeschaltet (und lässt
sich mit `--no-local-files` auch für stdio deaktivieren). Erlaubte Werte für
`attachment_type`: `Attachment`, `EmbeddedImage`, `Signature`, `Unknown`.
Obergrenze pro Datei: 25 MB.

---

## Konfiguration

Jede Option gibt es als Umgebungsvariable und als Kommandozeilenschalter; die
Kommandozeile gewinnt.

| Umgebungsvariable | Schalter | Standard | Bedeutung |
| --- | --- | --- | --- |
| `INET_BASE_URL` | `--base-url` | – | Basis-URL des HelpDesk, z. B. `https://helpdesk.example.com:9000` |
| `INET_TOKEN` | `--token` | – | Access-Token für `Authorization: Bearer …` |
| `INET_USERNAME` / `INET_PASSWORD` | `--username` / `--password` | – | Basic Auth als Alternative zum Token |
| `INET_TRANSPORT` | `--transport` | `stdio` | `stdio`, `http` oder `sse` |
| `INET_HOST` | `--host` | `127.0.0.1` | Bind-Adresse der HTTP-Transporte |
| `INET_PORT` | `--port` | `8000` | Port der HTTP-Transporte |
| `INET_HTTP_PATH` | `--http-path` | `/mcp` | Pfad des Streamable-HTTP-Endpunkts |
| `INET_TIMEOUT` | `--timeout` | `30` | HTTP-Timeout in Sekunden |
| `INET_VERIFY_TLS` | `--no-verify-tls` | `true` | TLS-Zertifikat des HelpDesk prüfen |
| `INET_CA_BUNDLE` | `--ca-bundle` | – | PEM-Datei mit den CA-Zertifikaten, denen vertraut wird (interne Firmen-CA) |
| `INET_READ_ONLY` | `--read-only` | `false` | Schreibende Tools ausblenden |
| `INET_ALLOW_URL_HEADER` | `--allow-url-header` | nur ohne `INET_BASE_URL` | `X-Inet-Base-Url`-Header erlauben |
| `INET_IGNORE_CLIENT_AUTH` | `--ignore-client-auth` | `false` | `Authorization`-Header der Clients ignorieren und immer die konfigurierten Zugangsdaten verwenden |
| `INET_ALLOW_LOCAL_FILES` | `--no-local-files` | `true` bei stdio, sonst `false` | Anhänge per Dateipfad erlauben |
| `INET_LOCALE` | `--locale` | `en` | Standardsprache der Suchphrase |
| `INET_NORMALIZE` | `--no-normalize` | `true` | Antworten aufbereiten (Anzeigewerte, ISO-Zeitstempel, HTML zu Text) |
| `INET_RETRIES` | `--retries` | `2` | Wiederholungen eines fehlgeschlagenen `GET`; `POST` wird nie wiederholt |
| `INET_POOL_SIZE` | `--pool-size` | `8` | Gleichzeitig offen gehaltene HelpDesk-Verbindungen |
| `INET_DRY_RUN` | `--dry-run` | `false` | Schreibende Aufrufe prüfen und zurückmelden, aber nicht senden |
| `INET_ALLOWED_ACTIONS` | `--allowed-actions` | – | Erlaubte Ticketaktions-Ids, kommasepariert |
| `INET_DENIED_ACTIONS` | `--denied-actions` | – | Gesperrte Ticketaktions-Ids, kommasepariert |
| `INET_DEFAULT_AUTOMAIL` | `--default-automail` | – | Standardwert für `ticketextension.automail` (`NEVER`, `NO_MAILS_TO_ENDUSER`, `SERVERSETTING`, `ALWAYS`) |

### HelpDesk hinter einer internen CA

Läuft der HelpDesk mit einem Zertifikat der eigenen Unternehmens-CA (etwa den
Active-Directory-Zertifikatsdiensten), kennt der Server dessen Aussteller
zunächst nicht: geprüft wird gegen das mitgelieferte
[certifi](https://pypi.org/project/certifi/)-Bundle mit den öffentlichen CAs,
**nicht** gegen den System-Truststore. Der Aufruf scheitert dann mit einem
`CERTIFICATE_VERIFY_FAILED` im Verbindungsfehler.

Der direkte Weg ist `--ca-bundle` mit der PEM-Datei der ausstellenden CA:

```bash
inet-helpdesk-mcp --base-url https://helpdesk.intern.example.com:9000 \
                  --ca-bundle /usr/local/share/ca-certificates/firmen-ca.crt
```

Genauso als Umgebungsvariable, z. B. im Env-File der systemd-Unit:

```bash
INET_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
```

Ohne die Option werden zusätzlich die Standardvariablen `SSL_CERT_FILE` und
`REQUESTS_CA_BUNDLE` berücksichtigt (in dieser Reihenfolge); erst wenn auch die
fehlen, bleibt es beim certifi-Bundle. Ein gesetztes `--ca-bundle` gewinnt
immer, und die angegebene Datei ist dann der **einzige** vertraute Truststore —
öffentliche CAs werden nicht zusätzlich akzeptiert. Zeigt der Pfad ins Leere,
bricht der Server sofort beim Start mit einer Konfigurationsmeldung ab.

**Alternative:** die CA einmal im System hinterlegen, dann genügt der Verweis
auf den System-Truststore (oder es reicht `SSL_CERT_FILE`):

```bash
sudo cp firmen-ca.crt /usr/local/share/ca-certificates/
sudo update-ca-certificates            # Debian/Ubuntu
# RHEL/Fedora: /etc/pki/ca-trust/source/anchors/ + update-ca-trust
inet-helpdesk-mcp --ca-bundle /etc/ssl/certs/ca-certificates.crt …
```

`--ca-bundle` und `--no-verify-tls` schließen einander aus — der Server beendet
sich mit einem Konfigurationsfehler, statt stillschweigend die Prüfung
abzuschalten. Welcher Truststore am Ende verwendet wird, zeigen der Startlog und
das Feld `tls` von `server_info`.

---

## Fehlersuche

* **`server_info` zuerst aufrufen** — es zeigt Basis-URL, Auth-Verfahren und ob
  eine Testabfrage gegen den HelpDesk funktioniert.
* **HTTP 401/403**: Token abgelaufen oder dem Benutzer fehlt das Recht „Web API".
* **HTTP 404 bei einem Ticket**: Ticket existiert nicht oder ist für diesen
  Benutzer nicht sichtbar; noch nicht autorisierte Tickets brauchen die
  Dispatcher-Rolle.
* **Verbindungsfehler**: Basis-URL inklusive Port prüfen (Standard des HelpDesk
  ist `9000`). Bei selbstsignierten Testsystemen hilft `--no-verify-tls`.
* **`CERTIFICATE_VERIFY_FAILED` / „unable to get local issuer certificate"**:
  Das HelpDesk-Zertifikat stammt aus einer internen CA — siehe
  [HelpDesk hinter einer internen CA](#helpdesk-hinter-einer-internen-ca).
* Mehr Details liefert `--log-level DEBUG` (Logs gehen auf stderr).

---

## Sicherheitshinweise

* Zugangsdaten stehen in Umgebungsvariablen bzw. im `Authorization`-Header und
  werden nie geloggt.
* Der Server macht genau das, was der angemeldete Benutzer darf — die
  Rechteprüfung bleibt beim HelpDesk.
* `apply_ticket_action` und `create_ticket` verändern Daten und können je nach
  Konfiguration E-Mails an Endanwender auslösen. Serverseitig verhindert das
  `--default-automail NEVER`, im einzelnen Aufruf das Aktionsargument
  `"ticketextension.automail": "NEVER"`. Zum Einfahren eines Agenten gegen ein
  Produktivsystem gibt es `--dry-run`.
* Jeder schreibende Aufruf wird mit Ticket-Id und Aktions-Id auf INFO protokolliert —
  ohne Token und ohne Passwort.
* `get_ticket` liefert standardmäßig alle Felder eines Tickets, inklusive
  personenbezogener Daten — mit `fields` gezielt einschränken.

---

## English summary

MCP server exposing the i-net HelpDesk Ticket Web-API: search, read, create and
act on tickets, with attachment support. Run it over **stdio** (credentials from
`INET_BASE_URL` + `INET_TOKEN`) or over **streamable HTTP**, where each client
authenticates by sending its own `Authorization: Bearer <token>` header — and,
when no base URL is configured, selects the HelpDesk instance with an
`X-Inet-Base-Url` header. Tools: `server_info`, `search_tickets`, `get_ticket`,
`get_ticket_conversation`, `list_ticket_actions`, `list_ticket_steps`,
`get_ticket_step`, `create_ticket`, `apply_ticket_action`. Answers are reshaped for
the agent (display values merged, ISO timestamps added, HTML step texts converted;
`raw: true` or `--no-normalize` opts out). Guard rails for the writing side:
`--read-only`, `--dry-run`, `--allowed-actions` / `--denied-actions` and
`--default-automail`.

## Lizenz

[MIT](LICENSE). Kein offizielles Produkt der i-net software GmbH.
Web-API-Dokumentation:
<https://docs.inetsoftware.de/helpdesk/help/webapi.ticket/p/ticket-web-api>

TDQS

A4/5.0

Scored across 8 tools

Disambiguation5/5

Each tool targets a distinct operation in the ticket lifecycle: creation, retrieval, search, action listing, action application, step listing, and step reading. No two tools have overlapping purposes, making it easy for an agent to select the correct tool.

Naming Consistency4/5

Six of eight tools follow a clear verb_noun pattern (e.g., create_ticket, search_tickets, get_ticket). However, server_info is a noun_noun format that breaks the pattern, preventing a perfect score.

Tool Count5/5

With 8 tools, the server covers core ticket operations without being bloated. The number is well-scoped for a focused HelpDesk MCP server, fitting the recommended range comfortably.

Completeness3/5

The server provides create, read, search, and action application, but lacks a general update tool for modifying ticket fields (e.g., priority, assignee). There is also no delete tool. This is a notable gap, though the action application covers workflow state changes.

Maintenance

ActivityMaintained
ResponsivenessNo issues