@dpesch/mantisbt-mcp-server 1.14.1 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -11,6 +11,51 @@ This project adheres to [Semantic Versioning](https://semver.org/).
11
11
 
12
12
  ---
13
13
 
14
+ ## [1.15.0] – 2026-10-31
15
+
16
+ ### Deprecated
17
+
18
+ - `add_monitor` — use `update_issue` with `add_monitors` instead; `add_monitor` will be removed in 2.0.0.
19
+ - `remove_monitor` — use `update_issue` with `remove_monitors` instead; `remove_monitor` will be removed in 2.0.0.
20
+ - `get_issues` — use `get_issue` with `ids` instead; `get_issues` will be removed in 2.0.0.
21
+ - `list_notes` — use `get_issue` with `select: "notes"` instead; `list_notes` will be removed in 2.0.0.
22
+ - `list_issue_files` — use `get_issue` with `select: "attachments"` instead; `list_issue_files` will be removed in 2.0.0.
23
+ - `get_project_categories`, `get_project_users`, `get_project_versions` — use `get_project` with `include: ["categories"]`, `["users"]` or `["versions"]` instead; they will be removed in 2.0.0.
24
+ - `find_project_member` — use `get_project` with `member_query` instead; `find_project_member` will be removed in 2.0.0. Unlike `find_project_member`, `get_project` has no `limit` and returns all matching users.
25
+ - `get_metadata_full` — use `get_metadata` with `detail: "full"` instead; `sync_metadata` — use `get_metadata` with `refresh: true`; `get_search_index_status` — use `get_metadata` (field `search_index`). All three will be removed in 2.0.0.
26
+ - `get_mcp_version`, `get_mantis_version` — use `get_version` instead; they will be removed in 2.0.0.
27
+ - `get_issue_fields`, `get_issue_enums` — use `get_issue_schema` instead; they will be removed in 2.0.0. The resource `mantis://enums` stays.
28
+ - `list_languages` — use `get_current_user` (field `language`) instead; `list_languages` will be removed in 2.0.0. Despite its name it never listed languages: MantisBT's `GET /lang` returns only the current user's language.
29
+ - `release_version` — use `update_version` with `released: true`, `timestamp`, and optionally `next_version` instead; `release_version` will be removed in 2.0.0.
30
+
31
+ ### Added
32
+
33
+ - `update_issue` accepts two new optional parameters `add_monitors` and `remove_monitors` (arrays of MantisBT login names, case-insensitive). `fields` may now be omitted when only monitor changes or a note are given. The response contains a `monitors` key with `added?`, `removed?`, `not_monitoring?`, and `current`. The steps run in order — fields, monitors, note. Once one of them succeeded, a later failure is reported inline (`monitors_error`, `note_error`) instead of failing the call, so changes that went through are not retried; only when every requested step failed does the call return an error. A user in both lists is rejected; a call with nothing to change is rejected. `dry_run` additionally shows `would_add_monitors` / `would_remove_monitors`.
34
+ - `update_issue` can edit existing notes: `fields.notes` takes `[{ id, text?, view_state? }]`. MantisBT has no REST route for editing a note, but its issue update applies note edits by ID. Note edits keep the note's current visibility unless `view_state` is given — the tool reads the current `view_state` first and sends it along. Note IDs that do not belong to the issue are rejected before anything is written. After the update the tool checks text and visibility in the response and reports edits MantisBT did not apply as `notes_error` (or as an error when the notes were the only field). `dry_run` shows the edit including the filled-in `view_state`. New notes are still added with the `note` parameter or `add_note`; no new tool.
35
+ - `get_issue` accepts `ids` (1–50 issue IDs) as an alternative to `id` — pass exactly one of the two. With `ids` the response is `{ issues, requested, found, failed }` as with `get_issues`, missing or inaccessible IDs give `null` at their position; `select` applies to every issue, and `view_url` is set even when `select` leaves out `id`.
36
+ - New tool `get_project`: master data of one project plus, on request, its categories, users and versions in one call. `include` takes any of `"categories"`, `"users"`, `"versions"`; `access_level` and `member_query` (case-insensitive substring search in login name, real name and email — all matches are returned) imply `"users"`, `obsolete` and `inherit` imply `"versions"`. Categories come without the `[All Projects] ` prefix and cost no extra request. Users come from the metadata cache when it is fresh and no `access_level` is given.
37
+ - Attachments in `get_issue` and `list_issue_files` now carry a `download_url`.
38
+ - `get_metadata` accepts `detail` (`"summary"`, the default and previous behaviour, or `"full"` for the complete raw cache) and `refresh` (`true` re-fetches all metadata first). With semantic search enabled the response also contains a `search_index` block (indexed, total, percent, last sync).
39
+ - New tool `get_version`: version of this MCP server and of the connected MantisBT installation in one response, `{ mcp_server: { version }, mantis: { installed_version, latest_version?, status? } }`. `check_latest` (default `true`) compares MantisBT with its latest GitHub release. If MantisBT cannot be reached, `mantis` holds an `error` and the MCP server version is still returned.
40
+ - New tool `get_issue_schema` (`project_id` optional): the valid `select` fields and the enum values (severity, status, priority, resolution, reproducibility) in one response.
41
+ - `list_issues` accepts `group_by` (`"status"`, `"handler"`, `"priority"`, `"category"`): the server counts all matching issues itself and returns `{ group_by, total, groups: [{ id, name, label?, count }] }` instead of an issue list. All filters still apply; `page`, `page_size` and `select` are ignored. Issues without a handler are counted as `"(unassigned)"`. The server pages through MantisBT 1,000 issues at a time with only the fields it needs (about 1 s for 7,700 issues); if it stops at its limit of 50,000 issues, the response says `"truncated": true`.
42
+ - `list_issues` with client-side filters (`status`, `assigned_to`, `reporter_id`, date filters) now says `"truncated": true` when its scan limit of 500 issues ended the search before the requested page was full. Before, such a page simply came back short without a hint.
43
+ - Token benchmark `npm run benchmark` (`scripts/benchmark-tokens.ts`): measures the response sizes of typical tasks with and without the server doing the work, against a live MantisBT (`BENCHMARK_PROJECT_ID`, `BENCHMARK_ISSUE_IDS`).
44
+ - New env var `MANTIS_TOOLSETS`: a comma-separated list of tool groups to offer — `issues`, `notes`, `links`, `files`, `projects`, `versions`, `meta`, `search` (case-insensitive). Unset or empty means all groups, as before. A deprecated tool belongs to the group of its replacement (e.g. `list_notes` to `issues`). The `search` group additionally needs `MANTIS_SEARCH_ENABLED=true`; without the group the search module is not started at all. An unknown group name stops the server at startup with a message listing the valid names.
45
+ - New env var `MANTIS_DISABLE_DESTRUCTIVE=true`: hides every tool marked as destructive — `delete_issue`, `delete_note`, `delete_version`, `remove_relationship`, `remove_monitor` — regardless of the groups.
46
+ - `get_current_user` now describes its fields and points out `language` — the user's MantisBT language, as a hint for the language of issue texts and notes.
47
+ - `update_version` accepts a new optional `next_version` parameter (only allowed together with `released: true`): creates a follow-up version in the same call. With `next_version` the response is `{ version: <updated>, next_version: <created> }` — or `{ version, next_version_error: <message> }` when creating the follow-up fails (the update itself stays in effect). Without `next_version` the response is unchanged (the updated version object).
48
+
49
+ ### Fixed
50
+
51
+ - Semantic search: the server no longer loads the whole vector index before it starts answering. On startup it read and parsed `index.json` only to count its entries — a number it needs only when `last_total.txt` is missing. With an index of about 70 MB (8,000 issues) the server is now ready roughly 0.2 s sooner on every session start; the background sync still loads the index afterwards.
52
+ - `list_issue_files` promised a `download_url` per attachment, but MantisBT sends it only via SOAP, never via REST. The server now builds it (`file_download.php?file_id=…&type=bug`), for `get_issue` as well.
53
+ - `update_issue` returned the raw MantisBT envelope `{ issues: [issue], view_url }` instead of the issue itself. MantisBT answers `PATCH /issues/{id}` with an `issues` array (unlike `POST /issues`, which answers `{ issue }`), but the tool expected `{ issue }`. The response is now the updated issue with `view_url`, the same shape as `get_issue` and `create_issue`. Combined with `remove_monitors`, the monitor list from the field update is reused, which saves one request.
54
+ - `add_monitor` always subscribed the owner of the API key instead of the given user. It sent `{ name }`, but MantisBT reads only a `users` array and falls back to the logged-in user when it is missing. The tool now sends `{ users: [{ name }] }` and checks the returned monitor list; the response is now `{ success, monitors }` instead of the full issue object.
55
+ - `remove_monitor` always failed with a 404: it called `DELETE /issues/{id}/monitors/{username}`, a route MantisBT (up to 2.28) does not have. The tool now reads the issue's monitors and sends `PATCH /issues/{id}` with the list minus the user — MantisBT treats `monitors` as the complete list. If the user is not a monitor, the call succeeds without changes (`removed: false`). Because MantisBT silently skips monitors the caller may not remove, the tool checks the result and returns an error if the user is still listed (removing other users requires `monitor_delete_others_bug_threshold`). The response now also lists the remaining monitors.
56
+
57
+ ---
58
+
14
59
  ## [1.14.1] – 2026-10-24
15
60
 
16
61
  ### Fixed
package/README.de.md CHANGED
@@ -83,6 +83,69 @@ npm run build
83
83
  | `MANTIS_SEARCH_MODEL` | – | `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | Embedding-Modell (wird beim ersten Start einmalig heruntergeladen, ~80 MB) |
84
84
  | `MANTIS_SEARCH_THREADS` | – | `1` | Anzahl der ONNX-Intra-Op-Threads für das Embedding-Modell. Standard ist 1, um CPU-Sättigung auf Mehrkernsystemen und in WSL zu verhindern. Nur erhöhen, wenn die Indexierungsgeschwindigkeit kritisch ist und der Host ausschließlich für diese Last vorgesehen ist. |
85
85
  | `MANTIS_UPLOAD_DIR` | – | – | Schränkt den `file_path` von `upload_file` auf dieses Verzeichnis ein (Pfad-Traversal via `../` wird blockiert). Im **stdio-Modus** ist `file_path` ohne diese Variable unbeschränkt. Im **HTTP-Modus** liest `file_path` aus dem Dateisystem des Servers und ist deshalb **deaktiviert, sofern diese Variable nicht gesetzt ist** – HTTP-Clients sollten stattdessen den Parameter `content` (Base64) verwenden. |
86
+ | `MANTIS_TOOLSETS` | – | – (alle) | Kommagetrennte Liste der anzubietenden Tool-Gruppen: `issues`, `notes`, `links`, `files`, `projects`, `versions`, `meta`, `search` (Groß-/Kleinschreibung egal). Nicht gesetzt oder leer bedeutet alle Gruppen. Die Gruppe `search` erfordert zusätzlich `MANTIS_SEARCH_ENABLED=true`; ohne die Gruppe wird das Such-Modul gar nicht gestartet. Ein unbekannter Gruppenname stoppt den Server beim Start mit einer Meldung, die die gültigen Namen auflistet. Siehe [Angebotene Tools einschränken](#angebotene-tools-einschränken) und das [Cookbook](docs/cookbook.de.md#nur-bestimmte-tool-gruppen-anbieten). |
87
+ | `MANTIS_DISABLE_DESTRUCTIVE` | – | `false` | Auf `true` setzen, um alle destruktiven Tools auszublenden – `delete_issue`, `delete_note`, `delete_version`, `remove_relationship`, `remove_monitor` – unabhängig von den aktiven Gruppen. Ausgeblendete Tools werden nicht aufgelistet und liefern »Tool … not found«, wenn sie aufgerufen werden. |
88
+
89
+ ### Angebotene Tools einschränken
90
+
91
+ Zwei Umgebungsvariablen reduzieren die dem Modell angebotene Tool-Oberfläche – sinnvoll, um den Kontext des Modells kürzer zu halten und versehentliche destruktive Operationen zu verhindern:
92
+
93
+ - **`MANTIS_TOOLSETS`** – nur die aufgelisteten Gruppen einbinden (kommagetrennt, Groß-/Kleinschreibung egal)
94
+ - **`MANTIS_DISABLE_DESTRUCTIVE=true`** – zusätzlich `delete_issue`, `delete_note`, `delete_version`, `remove_relationship` und `remove_monitor` ausblenden, unabhängig von den aktiven Gruppen
95
+
96
+ Ausgeblendete Tools werden nicht aufgelistet und liefern »Tool … not found«, wenn sie aufgerufen werden.
97
+
98
+ | Gruppe | Tools |
99
+ |---|---|
100
+ | `issues` | `get_issue`, `list_issues`, `create_issue`, `update_issue`, `delete_issue`¹, `get_issues`†, `list_notes`†, `list_issue_files`†, `add_monitor`†, `remove_monitor`†¹ |
101
+ | `notes` | `add_note`, `delete_note`¹ |
102
+ | `links` | `add_relationship`, `remove_relationship`¹, `attach_tags`, `detach_tag`, `list_tags` |
103
+ | `files` | `upload_file` |
104
+ | `projects` | `list_projects`, `get_project`, `list_filters`, `get_project_categories`†, `get_project_users`†, `get_project_versions`†, `find_project_member`† |
105
+ | `versions` | `create_version`, `update_version`, `delete_version`¹, `release_version`† |
106
+ | `meta` | `get_metadata`, `get_issue_schema`, `get_config`, `get_version`, `get_current_user`, `get_metadata_full`†, `sync_metadata`†, `get_search_index_status`†, `get_issue_fields`†, `get_issue_enums`†, `get_mcp_version`†, `get_mantis_version`†, `list_languages`† |
107
+ | `search` | `search_issues`, `rebuild_search_index` ² |
108
+
109
+ ¹ wird von `MANTIS_DISABLE_DESTRUCTIVE=true` ausgeblendet · † veraltet, wird in 2.0.0 entfernt · ² erfordert zusätzlich `MANTIS_SEARCH_ENABLED=true`; ohne die Gruppe `search` wird das Such-Modul gar nicht gestartet
110
+
111
+ Veraltete Tools gehören immer zur gleichen Gruppe wie ihr Ersatz – eine Gruppe zu deaktivieren blendet sowohl den alten als auch den neuen Namen aus.
112
+
113
+ **Beispiel A – Triage-Setup** (Issues, Notizen und Metadaten; keine destruktiven Operationen):
114
+
115
+ ```json
116
+ {
117
+ "mcpServers": {
118
+ "mantisbt": {
119
+ "command": "npx",
120
+ "args": ["-y", "@dpesch/mantisbt-mcp-server"],
121
+ "env": {
122
+ "MANTIS_BASE_URL": "https://deine-mantis-instanz.example.com/api/rest",
123
+ "MANTIS_API_KEY": "dein-api-token",
124
+ "MANTIS_TOOLSETS": "issues,notes,meta",
125
+ "MANTIS_DISABLE_DESTRUCTIVE": "true"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ **Beispiel B – alle Gruppen, nur Löschschutz** (36 von 41 Standard-Tools):
133
+
134
+ ```json
135
+ {
136
+ "mcpServers": {
137
+ "mantisbt": {
138
+ "command": "npx",
139
+ "args": ["-y", "@dpesch/mantisbt-mcp-server"],
140
+ "env": {
141
+ "MANTIS_BASE_URL": "https://deine-mantis-instanz.example.com/api/rest",
142
+ "MANTIS_API_KEY": "dein-api-token",
143
+ "MANTIS_DISABLE_DESTRUCTIVE": "true"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ```
86
149
 
87
150
  ## Verfügbare Tools
88
151
 
@@ -90,18 +153,18 @@ npm run build
90
153
 
91
154
  | Tool | Beschreibung |
92
155
  |---|---|
93
- | `get_issue` | Ein Issue anhand seiner ID abrufen; optionales `select` zur Feldauswahl reduziert die Antwortgröße |
94
- | `get_issues` | Mehrere Issues per ID in einem Aufruf abrufen (1–50 IDs); nicht zugängliche IDs liefern `null` an ihrer Position, statt den gesamten Aufruf abzubrechen |
95
- | `list_issues` | Issues nach Projekt, Status, Autor u.v.m. filtern; optionales `select` für Feldprojektion und `status` für clientseitige Statusfilterung – kanonische englische Statusnamen (z.B. `"new"`, `"resolved"`) werden per ID abgeglichen und funktionieren damit sprachunabhängig auf lokalisierten Installationen |
96
- | `create_issue` | Neues Issue anlegen; `severity` und `priority` müssen kanonische englische Namen sein (z.B. `minor`, `major`, `normal`, `high`) – `get_issue_enums` aufrufen, um alle gültigen Werte und deren lokalisierte Bezeichnungen zu sehen; optionaler `handler`-Parameter akzeptiert einen Benutzernamen als Alternative zu `handler_id` (wird gegen die Projektmitglieder aufgelöst); optionales `custom_fields` zum Setzen benutzerdefinierter Felder |
97
- | `update_issue` | Bestehendes Issue bearbeiten; Enum-Felder (`status`, `priority`, `severity`, `resolution`, `reproducibility`) akzeptieren kanonische englische Namen, lokalisierte Namen oder numerische IDs – der Server löst Namen automatisch zu IDs auf; unterstützt `custom_fields` sowie einen optionalen `note`-Parameter, der im selben Aufruf eine Notiz anhängt (z.B. die Begründung eines Status-Wechsels) |
156
+ | `get_issue` | Ein Issue per `id` oder mehrere per `ids` (1–50) in einem Aufruf abrufen – genau eines von beiden angeben; mit `ids` lautet die Antwort `{ issues, requested, found, failed }`, nicht zugängliche IDs liefern `null` an ihrer Position; optionales `select` zur Feldauswahl |
157
+ | `get_issues` | **Veraltet:** stattdessen `get_issue` mit `ids` verwenden – wird in 2.0.0 entfernt |
158
+ | `list_issues` | Issues nach Projekt, Status, Autor u.v.m. filtern; optionales `select` für Feldprojektion und `status` für clientseitige Statusfilterung – kanonische englische Statusnamen (z.B. `"new"`, `"resolved"`) werden per ID abgeglichen und funktionieren damit sprachunabhängig auf lokalisierten Installationen; `group_by` (`"status"`, `"handler"`, `"priority"`, `"category"`) liefert statt einer Issue-Liste Zählwerte pro Gruppe – alle Filter greifen weiterhin, `page`, `page_size` und `select` werden ignoriert |
159
+ | `create_issue` | Neues Issue anlegen; `severity` und `priority` müssen kanonische englische Namen sein (z.B. `minor`, `major`, `normal`, `high`) – `get_issue_schema` aufrufen, um alle gültigen Werte und deren lokalisierte Bezeichnungen zu sehen; optionaler `handler`-Parameter akzeptiert einen Benutzernamen als Alternative zu `handler_id` (wird gegen die Projektmitglieder aufgelöst); optionales `custom_fields` zum Setzen benutzerdefinierter Felder |
160
+ | `update_issue` | Bestehendes Issue bearbeiten; Enum-Felder (`status`, `priority`, `severity`, `resolution`, `reproducibility`) akzeptieren kanonische englische Namen, lokalisierte Namen oder numerische IDs – der Server löst Namen automatisch zu IDs auf; unterstützt `custom_fields` sowie einen optionalen `note`-Parameter, der im selben Aufruf eine Notiz anhängt; `fields` darf entfallen, wenn nur `add_monitors`, `remove_monitors` oder `note` angegeben werden; mit `add_monitors` / `remove_monitors` (Arrays von Login-Namen) können Beobachter hinzugefügt oder entfernt werden – die Antwort enthält einen `monitors`-Schlüssel mit der aktuellen und der geänderten Beobachterliste; mit `fields.notes` lassen sich bestehende Notizen per ID bearbeiten (Notiz-ID aus `get_issue`) |
98
161
  | `delete_issue` | Issue löschen |
99
162
 
100
163
  ### Notizen
101
164
 
102
165
  | Tool | Beschreibung |
103
166
  |---|---|
104
- | `list_notes` | Alle Notizen eines Issues auflisten |
167
+ | `list_notes` | **Veraltet:** stattdessen `get_issue` mit `select: "notes"` verwenden – wird in 2.0.0 entfernt |
105
168
  | `add_note` | Notiz zu einem Issue hinzufügen |
106
169
  | `delete_note` | Notiz löschen |
107
170
 
@@ -109,7 +172,7 @@ npm run build
109
172
 
110
173
  | Tool | Beschreibung |
111
174
  |---|---|
112
- | `list_issue_files` | Anhänge eines Issues auflisten |
175
+ | `list_issue_files` | **Veraltet:** stattdessen `get_issue` mit `select: "attachments"` verwenden – wird in 2.0.0 entfernt |
113
176
  | `upload_file` | Datei an ein Issue anhängen – bevorzugt: lokaler `file_path` (der Server liest und kodiert die Datei automatisch); Fallback: Base64-kodiertes `content` + `filename` (nur verwenden, wenn `file_path` nicht verfügbar ist) |
114
177
 
115
178
  ### Beziehungen
@@ -123,14 +186,14 @@ npm run build
123
186
 
124
187
  | Tool | Beschreibung |
125
188
  |---|---|
126
- | `add_monitor` | Einen Benutzer als Beobachter eines Issues eintragen |
127
- | `remove_monitor` | Benutzer als Beobachter eines Issues austragen |
189
+ | `add_monitor` | **Veraltet:** stattdessen `update_issue` mit `add_monitors` verwenden – wird in 2.0.0 entfernt |
190
+ | `remove_monitor` | **Veraltet:** stattdessen `update_issue` mit `remove_monitors` verwenden – wird in 2.0.0 entfernt |
128
191
 
129
192
  ### Tags
130
193
 
131
194
  | Tool | Beschreibung |
132
195
  |---|---|
133
- | `list_tags` | Alle verfügbaren Tags auflisten; greift auf den Metadaten-Cache zurück, wenn `GET /tags` mit 404 antwortet (vorher `sync_metadata` ausführen) |
196
+ | `list_tags` | Alle verfügbaren Tags auflisten; greift auf den Metadaten-Cache zurück, wenn `GET /tags` mit 404 antwortet (vorher `get_metadata` mit `refresh: true` ausführen) |
134
197
  | `attach_tags` | Tags an ein Issue hängen |
135
198
  | `detach_tag` | Tag von einem Issue entfernen |
136
199
 
@@ -138,15 +201,16 @@ npm run build
138
201
 
139
202
  | Tool | Beschreibung |
140
203
  |---|---|
141
- | `list_projects` | Alle zugänglichen Projekte auflisten; gibt normalisierte Projektdaten zurück (konsistent mit dem `sync_metadata`-Cache) |
142
- | `get_project_versions` | Versionen eines Projekts abrufen; optionale Booleans `obsolete` und `inherit` für veraltete bzw. vom Elternprojekt geerbte Versionen; jede Version enthält ein `timestamp`-Feld (Versionsdatum); zum Ändern siehe `create_version`, `update_version`, `release_version`, `delete_version` |
204
+ | `list_projects` | Alle zugänglichen Projekte auflisten; gibt normalisierte Projektdaten zurück (konsistent mit dem Metadaten-Cache) |
205
+ | `get_project` | Stammdaten eines Projekts (id, name, status, enabled, Unterprojekte) sowie auf Anfrage Kategorien, Benutzer und/oder Versionen in einem Aufruf; `access_level` und `member_query` implizieren `users`; `obsolete`/`inherit` implizieren `versions` |
206
+ | `get_project_versions` | **Veraltet:** stattdessen `get_project` mit `include: ["versions"]` verwenden – wird in 2.0.0 entfernt |
143
207
  | `create_version` | Neue Version in einem Projekt erstellen; gibt das erstellte Versionsobjekt zurück (id, name, description, released, obsolete, timestamp); erfordert `manage_project_threshold` (Standard: Manager) |
144
- | `update_version` | Bestehende Version aktualisieren; nur übergebene Felder werden geändert (mindestens eines erforderlich); Umbenennen schreibt version/target_version/fixed_in_version aller referenzierenden Issues neu; erfordert `manage_project_threshold` (Standard: Manager) |
145
- | `release_version` | Version als veröffentlicht markieren und Datum setzen (Standard: jetzt); optional eine Folgeversion im gleichen Schritt anlegen via `next_version`; erfordert `manage_project_threshold` (Standard: Manager) |
208
+ | `update_version` | Bestehende Version aktualisieren; nur übergebene Felder werden geändert (mindestens eines erforderlich); Umbenennen schreibt version/target_version/fixed_in_version aller referenzierenden Issues neu; optionales `next_version` (nur zusammen mit `released: true`) legt eine Folgeversion im gleichen Aufruf an; erfordert `manage_project_threshold` (Standard: Manager) |
209
+ | `release_version` | **Veraltet:** stattdessen `update_version` mit `released: true`, `timestamp` und optional `next_version` verwenden – wird in 2.0.0 entfernt |
146
210
  | `delete_version` | Version dauerhaft löschen – nicht umkehrbar, MantisBT leert version/target_version/fixed_in_version aller referenzierenden Issues; `update_version` mit `obsolete=true` als nicht-destruktive Alternative bevorzugen; erfordert `manage_project_threshold` (Standard: Manager) |
147
- | `get_project_categories` | Kategorien eines Projekts abrufen |
148
- | `get_project_users` | Benutzer eines Projekts abrufen |
149
- | `find_project_member` | Projektmitglieder nach Name, Realname oder E-Mail suchen (Teilstring-Suche, Groß-/Kleinschreibung ignoriert); optionale Parameter `query` und `limit` (Standard 10, max. 100); Cache-first |
211
+ | `get_project_categories` | **Veraltet:** stattdessen `get_project` mit `include: ["categories"]` verwenden – wird in 2.0.0 entfernt |
212
+ | `get_project_users` | **Veraltet:** stattdessen `get_project` mit `include: ["users"]` verwenden – wird in 2.0.0 entfernt |
213
+ | `find_project_member` | **Veraltet:** stattdessen `get_project` mit `member_query` verwenden (kein `limit`, liefert alle Treffer) – wird in 2.0.0 entfernt |
150
214
 
151
215
  ### Semantische Suche *(optional)*
152
216
 
@@ -164,7 +228,7 @@ Aktivierung mit `MANTIS_SEARCH_ENABLED=true`.
164
228
  |---|---|
165
229
  | `search_issues` | Natürlichsprachige Suche über alle indizierten Issues – liefert Top-N-Ergebnisse mit Cosine-Similarity-Score; optionales `select` (kommagetrennte Feldnamen) reichert jedes Ergebnis mit den angeforderten Issue-Feldern an; optionales `highlight` (boolean, Standard `false`) fügt je Ergebnis ein `highlights`-Feld mit keyword-basierten Ausschnitten aus `summary` und `description` hinzu (Treffer werden in `**fett**` dargestellt) |
166
230
  | `rebuild_search_index` | Suchindex aufbauen oder aktualisieren; `full: true` löscht und baut ihn vollständig neu |
167
- | `get_search_index_status` | Aktuellen Füllstand des Suchindex zurückgeben: wie viele Issues bereits indiziert sind im Verhältnis zur Gesamtanzahl, plus Zeitstempel der letzten Synchronisation |
231
+ | `get_search_index_status` | **Veraltet:** stattdessen `get_metadata` (Feld `search_index`) verwenden – wird in 2.0.0 entfernt |
168
232
 
169
233
  #### Welches Backend wählen?
170
234
 
@@ -186,17 +250,41 @@ npm install sqlite-vec better-sqlite3
186
250
 
187
251
  | Tool | Beschreibung |
188
252
  |---|---|
189
- | `get_issue_fields` | Alle gültigen Feldnamen für den `select`-Parameter von `list_issues` zurückgeben |
190
- | `get_metadata` | Kompakte Metadaten-Zusammenfassung abrufen: Anzahl Projekte/Tags und Anzahl Benutzer/Versionen/Kategorien je Projekt; für vollständige Arrays `get_metadata_full` verwenden |
191
- | `get_metadata_full` | Vollständigen rohen Metadaten-Cache als minifiziertes JSON zurückgeben (alle Projekte mit vollständigen Feldern, Benutzer/Versionen/Kategorien je Projekt, alle Tags) |
192
- | `sync_metadata` | Metadaten-Cache neu befüllen |
253
+ | `get_metadata` | Gecachte Metadaten abrufen: `detail: "summary"` (Standard) liefert Projekt-/Tag-Zählwerte und Benutzer-/Versions-/Kategorie-Zählwerte je Projekt; `detail: "full"` liefert den vollständigen rohen Cache; `refresh: true` lädt alle Daten zuerst von MantisBT nach; mit aktivierter semantischer Suche enthält die Antwort zusätzlich einen `search_index`-Block |
254
+ | `get_version` | Version dieses MCP-Servers und der verbundenen MantisBT-Installation in einer Antwort; `check_latest` (Standard `true`) vergleicht MantisBT mit dem neuesten GitHub-Release; `mantis` enthält `error`, wenn MantisBT nicht erreichbar ist |
255
+ | `get_issue_schema` | Gültige `select`-Felder (`fields`, `fields_source`) und Enum-Werte (`enums`: severity, status, priority, resolution, reproducibility) für diesen MantisBT-Server in einer Antwort; optionale `project_id` schränkt die Feldermittlung ein – vor `create_issue` / `update_issue` oder beim Aufbau eines `select`-Strings verwenden |
256
+ | `get_issue_fields` | **Veraltet:** stattdessen `get_issue_schema` verwenden – wird in 2.0.0 entfernt |
257
+ | `get_issue_enums` | **Veraltet:** stattdessen `get_issue_schema` verwenden; die Ressource `mantis://enums` bleibt – wird in 2.0.0 entfernt |
258
+ | `get_metadata_full` | **Veraltet:** stattdessen `get_metadata` mit `detail: "full"` verwenden – wird in 2.0.0 entfernt |
259
+ | `sync_metadata` | **Veraltet:** stattdessen `get_metadata` mit `refresh: true` verwenden – wird in 2.0.0 entfernt |
193
260
  | `list_filters` | Gespeicherte Filter auflisten |
194
- | `get_current_user` | Eigenes Benutzerprofil abrufen |
195
- | `list_languages` | Verfügbare Sprachen auflisten |
261
+ | `get_current_user` | Eigenes Benutzerprofil abrufen – enthält `language` (die MantisBT-Sprache des Benutzers, ein Hinweis auf die Sprache für Issue-Texte und Notizen) |
262
+ | `list_languages` | **Veraltet:** stattdessen `get_current_user` (Feld `language`) verwenden – wird in 2.0.0 entfernt |
196
263
  | `get_config` | Server-Konfiguration (Basis-URL, Cache-TTL) anzeigen |
197
- | `get_issue_enums` | Gültige ID/Name-Paare für alle Enum-Felder zurückgeben (Severity, Status, Priority, Resolution, Reproducibility) – vor `create_issue` / `update_issue` verwenden, um korrekte Werte nachzuschlagen; auf lokalisierten Installationen kann jeder Eintrag ein `canonical_name`-Feld mit dem englischen Standard-API-Namen enthalten |
198
- | `get_mantis_version` | MantisBT-Version abrufen und auf Updates prüfen |
199
- | `get_mcp_version` | Version dieser mantisbt-mcp-server-Instanz zurückgeben |
264
+ | `get_mantis_version` | **Veraltet:** stattdessen `get_version` verwenden – wird in 2.0.0 entfernt |
265
+ | `get_mcp_version` | **Veraltet:** stattdessen `get_version` verwenden – wird in 2.0.0 entfernt |
266
+
267
+ ## Token-Verbrauch
268
+
269
+ Der Server übernimmt das Zählen und die Feldprojektion selbst – das Modell erhält eine kompakte Zusammenfassung, statt rohe Issue-Listen zu lesen. Gemessen am 2026-10-31 an einer MantisBT-2.28-Installation (604 Issues, fünf Issues mit langen Beschreibungen und Notizen):
270
+
271
+ | Aufgabe | Vorher | Tokens | Nachher | Tokens | Ersparnis |
272
+ |---|---|---:|---|---:|---:|
273
+ | Statusübersicht eines Projekts (604 Issues) | `list_issues`, alle Seiten | 1.274.398 | `list_issues` + `group_by: "status"` | 165 | 99,9 % |
274
+ | Offene Issues je Bearbeiter | `list_issues` Status `"open"` + `select`, alle Seiten ¹ | 2.686 | `list_issues` + `group_by: "handler"` | 108 | 95,9 % |
275
+ | 5 Issues mit ausgewählten Feldern | `get_issue` ids, ohne `select` | 16.272 | `get_issue` ids + `select` | 623 | 96,1 % |
276
+
277
+ ¹ `list_issues` durchsucht bei aktiven clientseitigen Filtern höchstens 500 Issues – der »Vorher«-Wert ist daher zu niedrig angesetzt.
278
+
279
+ Tokens geschätzt als Zeichen / 4. Zur Reproduktion an der eigenen Instanz (erfordert einen Git-Checkout, nicht per npx verfügbar):
280
+
281
+ ```bash
282
+ MANTIS_BASE_URL=… MANTIS_API_KEY=… \
283
+ BENCHMARK_PROJECT_ID=12 BENCHMARK_ISSUE_IDS=101,102,103,104,105 \
284
+ npm run benchmark
285
+ ```
286
+
287
+ Anmeldedaten können auch aus `.env.local` kommen.
200
288
 
201
289
  ## Verfügbare Ressourcen
202
290
 
@@ -205,7 +293,7 @@ MCP-Ressourcen sind URI-adressierbare, schreibgeschützte Daten, die Clients dir
205
293
  | Ressource-URI | Beschreibung |
206
294
  |---|---|
207
295
  | `mantis://me` | Profil des authentifizierten API-Benutzers (Live-Abruf) |
208
- | `mantis://projects` | Alle zugänglichen MantisBT-Projekte als kompakte Liste (Cache-basiert, Aktualisierung via `sync_metadata`) |
296
+ | `mantis://projects` | Alle zugänglichen MantisBT-Projekte als kompakte Liste (Cache-basiert, Aktualisierung via `get_metadata` mit `refresh: true`) |
209
297
  | `mantis://projects/{id}` | Kombinierte Projektansicht: Projektfelder + Benutzer + Versionen + Kategorien in einem Aufruf; Cache-first, List-Support zur Aufzählung aller verfügbaren Projekt-URIs |
210
298
  | `mantis://enums` | Gültige Werte für alle Issue-Enum-Felder: Severity, Priority, Status, Resolution, Reproducibility (Live-Abruf) |
211
299
 
@@ -252,6 +340,7 @@ npm run dev # Watch-Modus für Entwicklung
252
340
  npm test # Tests ausführen (vitest)
253
341
  npm run test:watch # Tests im Watch-Modus
254
342
  npm run test:coverage # Coverage-Report
343
+ npm run benchmark # Token-Benchmark gegen eine Live-MantisBT-Instanz (siehe Token-Verbrauch oben)
255
344
  ```
256
345
 
257
346
  ## Lizenz
package/README.md CHANGED
@@ -83,6 +83,69 @@ npm run build
83
83
  | `MANTIS_SEARCH_MODEL` | – | `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | Embedding model name (downloaded once on first use, ~80 MB) |
84
84
  | `MANTIS_SEARCH_THREADS` | – | `1` | Number of ONNX intra-op threads for the embedding model. Default is 1 to prevent CPU saturation on multi-core machines and WSL. Increase only if index rebuild speed matters and the host is dedicated to this workload. |
85
85
  | `MANTIS_UPLOAD_DIR` | – | – | Restrict `upload_file`'s `file_path` to files within this directory (path traversal via `../` is blocked). In **stdio mode** `file_path` is unrestricted unless this is set. In **HTTP mode** `file_path` reads from the server's filesystem, so it is **disabled unless this variable is set** — HTTP clients should upload via the `content` (Base64) parameter instead. |
86
+ | `MANTIS_TOOLSETS` | – | – (all) | Comma-separated list of tool groups to offer: `issues`, `notes`, `links`, `files`, `projects`, `versions`, `meta`, `search` (case-insensitive). Unset or empty means all groups. The `search` group additionally requires `MANTIS_SEARCH_ENABLED=true`; without the group the search module is not started at all. An unknown group name stops the server at startup with a message listing the valid names. See [Limit the offered tools](#limit-the-offered-tools) and the [cookbook](docs/cookbook.md#offer-only-some-tool-groups). |
87
+ | `MANTIS_DISABLE_DESTRUCTIVE` | – | `false` | Set to `true` to hide every destructive tool — `delete_issue`, `delete_note`, `delete_version`, `remove_relationship`, `remove_monitor` — regardless of the active groups. Hidden tools are not listed and return "Tool … not found" when called. |
88
+
89
+ ### Limit the offered tools
90
+
91
+ Two env vars reduce the tool surface offered to the model — useful to keep the model's context shorter and to prevent accidental destructive operations:
92
+
93
+ - **`MANTIS_TOOLSETS`** — include only the listed groups (comma-separated, case-insensitive)
94
+ - **`MANTIS_DISABLE_DESTRUCTIVE=true`** — additionally hide `delete_issue`, `delete_note`, `delete_version`, `remove_relationship`, and `remove_monitor` regardless of the active groups
95
+
96
+ Hidden tools are not listed and return "Tool … not found" when called.
97
+
98
+ | Group | Tools |
99
+ |---|---|
100
+ | `issues` | `get_issue`, `list_issues`, `create_issue`, `update_issue`, `delete_issue`¹, `get_issues`†, `list_notes`†, `list_issue_files`†, `add_monitor`†, `remove_monitor`†¹ |
101
+ | `notes` | `add_note`, `delete_note`¹ |
102
+ | `links` | `add_relationship`, `remove_relationship`¹, `attach_tags`, `detach_tag`, `list_tags` |
103
+ | `files` | `upload_file` |
104
+ | `projects` | `list_projects`, `get_project`, `list_filters`, `get_project_categories`†, `get_project_users`†, `get_project_versions`†, `find_project_member`† |
105
+ | `versions` | `create_version`, `update_version`, `delete_version`¹, `release_version`† |
106
+ | `meta` | `get_metadata`, `get_issue_schema`, `get_config`, `get_version`, `get_current_user`, `get_metadata_full`†, `sync_metadata`†, `get_search_index_status`†, `get_issue_fields`†, `get_issue_enums`†, `get_mcp_version`†, `get_mantis_version`†, `list_languages`† |
107
+ | `search` | `search_issues`, `rebuild_search_index` ² |
108
+
109
+ ¹ hidden by `MANTIS_DISABLE_DESTRUCTIVE=true` · † deprecated, removed in 2.0.0 · ² additionally requires `MANTIS_SEARCH_ENABLED=true`; without the `search` group the search module is not started at all
110
+
111
+ Deprecated tools always belong to the same group as their replacement — disabling a group hides old and new name together.
112
+
113
+ **Example A — triage setup** (issues, notes, and metadata; no destructive operations):
114
+
115
+ ```json
116
+ {
117
+ "mcpServers": {
118
+ "mantisbt": {
119
+ "command": "npx",
120
+ "args": ["-y", "@dpesch/mantisbt-mcp-server"],
121
+ "env": {
122
+ "MANTIS_BASE_URL": "https://your-mantis.example.com/api/rest",
123
+ "MANTIS_API_KEY": "your-api-token",
124
+ "MANTIS_TOOLSETS": "issues,notes,meta",
125
+ "MANTIS_DISABLE_DESTRUCTIVE": "true"
126
+ }
127
+ }
128
+ }
129
+ }
130
+ ```
131
+
132
+ **Example B — all groups, delete-protection only** (36 of the 41 default tools):
133
+
134
+ ```json
135
+ {
136
+ "mcpServers": {
137
+ "mantisbt": {
138
+ "command": "npx",
139
+ "args": ["-y", "@dpesch/mantisbt-mcp-server"],
140
+ "env": {
141
+ "MANTIS_BASE_URL": "https://your-mantis.example.com/api/rest",
142
+ "MANTIS_API_KEY": "your-api-token",
143
+ "MANTIS_DISABLE_DESTRUCTIVE": "true"
144
+ }
145
+ }
146
+ }
147
+ }
148
+ ```
86
149
 
87
150
  ## Available tools
88
151
 
@@ -90,18 +153,18 @@ npm run build
90
153
 
91
154
  | Tool | Description |
92
155
  |---|---|
93
- | `get_issue` | Retrieve an issue by its numeric ID; optional `select` for field projection to reduce response size |
94
- | `get_issues` | Retrieve multiple issues by ID in one call (1–50 IDs); missing or inaccessible IDs return `null` at their position instead of failing the call |
95
- | `list_issues` | Filter issues by project, status, author, and more; optional `select` for field projection and `status` for client-side status filtering — canonical English status names (e.g. `"new"`, `"resolved"`) are matched by ID, making the filter language-independent on localized installations |
96
- | `create_issue` | Create a new issue; `severity` and `priority` must be canonical English names (e.g. `minor`, `major`, `normal`, `high`) — call `get_issue_enums` to see all valid values and their localized labels; optional `handler` parameter accepts a username as alternative to `handler_id` (resolved against project members); optional `custom_fields` to set custom field values |
97
- | `update_issue` | Update an existing issue; enum fields (`status`, `priority`, `severity`, `resolution`, `reproducibility`) accept canonical English names, localized names, or numeric IDs — the server resolves names to IDs automatically; supports `custom_fields` and an optional `note` parameter that appends a note in the same call (e.g. the reason for a status change) |
156
+ | `get_issue` | Retrieve an issue by `id`, or several by `ids` (1–50) in one call — pass exactly one; with `ids` returns `{ issues, requested, found, failed }`, missing or inaccessible IDs give `null` at their position; optional `select` for field projection |
157
+ | `get_issues` | **Deprecated:** use `get_issue` with `ids` instead — will be removed in 2.0.0 |
158
+ | `list_issues` | Filter issues by project, status, author, and more; optional `select` for field projection and `status` for client-side status filtering — canonical English status names (e.g. `"new"`, `"resolved"`) are matched by ID, making the filter language-independent on localized installations; `group_by` (`"status"`, `"handler"`, `"priority"`, `"category"`) returns counts per group instead of an issue list — all filters still apply, `page`, `page_size`, and `select` are ignored |
159
+ | `create_issue` | Create a new issue; `severity` and `priority` must be canonical English names (e.g. `minor`, `major`, `normal`, `high`) — call `get_issue_schema` to see all valid values and their localized labels; optional `handler` parameter accepts a username as alternative to `handler_id` (resolved against project members); optional `custom_fields` to set custom field values |
160
+ | `update_issue` | Update an existing issue; enum fields (`status`, `priority`, `severity`, `resolution`, `reproducibility`) accept canonical English names, localized names, or numeric IDs — the server resolves names to IDs automatically; supports `custom_fields` and an optional `note` parameter that appends a note in the same call; `fields` may be omitted when only `add_monitors`, `remove_monitors`, or `note` are given; use `add_monitors` / `remove_monitors` (arrays of login names) to add or remove watchers — the response includes a `monitors` key with the current and changed monitor lists; use `fields.notes` to edit existing notes by ID (note ID from `get_issue`) |
98
161
  | `delete_issue` | Delete an issue |
99
162
 
100
163
  ### Notes
101
164
 
102
165
  | Tool | Description |
103
166
  |---|---|
104
- | `list_notes` | List all notes of an issue |
167
+ | `list_notes` | **Deprecated:** use `get_issue` with `select: "notes"` instead — will be removed in 2.0.0 |
105
168
  | `add_note` | Add a note to an issue |
106
169
  | `delete_note` | Delete a note |
107
170
 
@@ -109,7 +172,7 @@ npm run build
109
172
 
110
173
  | Tool | Description |
111
174
  |---|---|
112
- | `list_issue_files` | List attachments of an issue |
175
+ | `list_issue_files` | **Deprecated:** use `get_issue` with `select: "attachments"` instead — will be removed in 2.0.0 |
113
176
  | `upload_file` | Upload a file to an issue — preferred: local `file_path` (server reads and encodes automatically); fallback: Base64-encoded `content` + `filename` (use only when `file_path` is not available) |
114
177
 
115
178
  ### Relationships
@@ -123,14 +186,14 @@ npm run build
123
186
 
124
187
  | Tool | Description |
125
188
  |---|---|
126
- | `add_monitor` | Add a user as a monitor of an issue |
127
- | `remove_monitor` | Remove a user as a monitor of an issue |
189
+ | `add_monitor` | **Deprecated:** use `update_issue` with `add_monitors` instead — will be removed in 2.0.0 |
190
+ | `remove_monitor` | **Deprecated:** use `update_issue` with `remove_monitors` instead — will be removed in 2.0.0 |
128
191
 
129
192
  ### Tags
130
193
 
131
194
  | Tool | Description |
132
195
  |---|---|
133
- | `list_tags` | List all available tags; falls back to the metadata cache when `GET /tags` returns 404 (run `sync_metadata` first to populate) |
196
+ | `list_tags` | List all available tags; falls back to the metadata cache when `GET /tags` returns 404 (run `get_metadata` with `refresh: true` first to populate) |
134
197
  | `attach_tags` | Attach tags to an issue |
135
198
  | `detach_tag` | Remove a tag from an issue |
136
199
 
@@ -138,15 +201,16 @@ npm run build
138
201
 
139
202
  | Tool | Description |
140
203
  |---|---|
141
- | `list_projects` | List all accessible projects; returns normalized project data (consistent with `sync_metadata` cache) |
142
- | `get_project_versions` | Get versions of a project; optional `obsolete` and `inherit` booleans to include obsolete or parent-inherited versions; each version includes a `timestamp` field (version date); see `create_version`, `update_version`, `release_version`, `delete_version` to modify versions |
204
+ | `list_projects` | List all accessible projects; returns normalized project data (consistent with the metadata cache) |
205
+ | `get_project` | Get one project's master data (id, name, status, enabled, subprojects) plus, on request, its categories, users and/or versions in one call; `access_level` and `member_query` imply `users`; `obsolete`/`inherit` imply `versions` |
206
+ | `get_project_versions` | **Deprecated:** use `get_project` with `include: ["versions"]` instead — will be removed in 2.0.0 |
143
207
  | `create_version` | Create a new version in a project; returns the created version object (id, name, description, released, obsolete, timestamp); requires `manage_project_threshold` (default: manager) |
144
- | `update_version` | Update an existing version; only the fields you pass are changed (at least one required); renaming rewrites version/target_version/fixed_in_version on all referencing issues; requires `manage_project_threshold` (default: manager) |
145
- | `release_version` | Mark a version as released and set its date (default: now); optionally create a follow-up version in the same step via `next_version`; requires `manage_project_threshold` (default: manager) |
208
+ | `update_version` | Update an existing version; only the fields you pass are changed (at least one required); renaming rewrites version/target_version/fixed_in_version on all referencing issues; optional `next_version` (only with `released: true`) creates a follow-up version in the same call; requires `manage_project_threshold` (default: manager) |
209
+ | `release_version` | **Deprecated:** use `update_version` with `released: true`, `timestamp`, and optionally `next_version` instead — will be removed in 2.0.0 |
146
210
  | `delete_version` | Permanently delete a version — irreversible, MantisBT clears version/target_version/fixed_in_version on all referencing issues; prefer `update_version` with `obsolete=true` as the non-destructive alternative; requires `manage_project_threshold` (default: manager) |
147
- | `get_project_categories` | Get categories of a project |
148
- | `get_project_users` | Get users of a project |
149
- | `find_project_member` | Search project members by name, real name, or email (case-insensitive substring match); optional `query` and `limit` (default 10, max 100); cache-first |
211
+ | `get_project_categories` | **Deprecated:** use `get_project` with `include: ["categories"]` instead — will be removed in 2.0.0 |
212
+ | `get_project_users` | **Deprecated:** use `get_project` with `include: ["users"]` instead — will be removed in 2.0.0 |
213
+ | `find_project_member` | **Deprecated:** use `get_project` with `member_query` instead (no `limit`, returns all matches) — will be removed in 2.0.0 |
150
214
 
151
215
  ### Semantic search *(optional)*
152
216
 
@@ -164,7 +228,7 @@ Activate with `MANTIS_SEARCH_ENABLED=true`.
164
228
  |---|---|
165
229
  | `search_issues` | Natural language search over all indexed issues — returns top-N results with cosine similarity score; optional `select` (comma-separated field names) enriches each result with the requested issue fields; optional `highlight` (boolean, default `false`) adds a `highlights` field per result with keyword-matched excerpts from `summary` and `description` (matched terms shown in `**bold**`) |
166
230
  | `rebuild_search_index` | Build or update the search index; `full: true` clears and rebuilds from scratch |
167
- | `get_search_index_status` | Return the current fill level of the search index: how many issues are indexed vs. total, and the timestamp of the last sync |
231
+ | `get_search_index_status` | **Deprecated:** use `get_metadata` (field `search_index`) instead — will be removed in 2.0.0 |
168
232
 
169
233
  #### Which backend to choose?
170
234
 
@@ -186,17 +250,41 @@ npm install sqlite-vec better-sqlite3
186
250
 
187
251
  | Tool | Description |
188
252
  |---|---|
189
- | `get_issue_fields` | Return all field names valid for the `select` parameter of `list_issues` |
190
- | `get_metadata` | Retrieve a compact metadata summary: project/tag counts and per-project user/version/category counts; use `get_metadata_full` for complete arrays |
191
- | `get_metadata_full` | Return the full raw metadata cache as minified JSON (all projects with complete fields, users/versions/categories per project, all tags) |
192
- | `sync_metadata` | Refresh the metadata cache |
253
+ | `get_metadata` | Retrieve cached metadata: `detail: "summary"` (default) returns project/tag counts and per-project user/version/category counts; `detail: "full"` returns the complete raw cache; `refresh: true` re-fetches from MantisBT first; with semantic search enabled the response also contains a `search_index` block |
254
+ | `get_version` | Version of this MCP server and of the connected MantisBT installation in one response; `check_latest` (default `true`) compares MantisBT with the latest GitHub release; `mantis` holds `error` when MantisBT cannot be reached |
255
+ | `get_issue_schema` | Valid `select` fields (`fields`, `fields_source`) and enum values (`enums`: severity, status, priority, resolution, reproducibility) for this MantisBT server in one response; optional `project_id` scopes the field discovery — use before `create_issue` / `update_issue` or when building a `select` string |
256
+ | `get_issue_fields` | **Deprecated:** use `get_issue_schema` instead — will be removed in 2.0.0 |
257
+ | `get_issue_enums` | **Deprecated:** use `get_issue_schema` instead; the resource `mantis://enums` stays — will be removed in 2.0.0 |
258
+ | `get_metadata_full` | **Deprecated:** use `get_metadata` with `detail: "full"` instead — will be removed in 2.0.0 |
259
+ | `sync_metadata` | **Deprecated:** use `get_metadata` with `refresh: true` instead — will be removed in 2.0.0 |
193
260
  | `list_filters` | List saved filters |
194
- | `get_current_user` | Retrieve your own user profile |
195
- | `list_languages` | List available languages |
261
+ | `get_current_user` | Retrieve your own user profile — includes `language` (the user's MantisBT language, a hint for which language to use in issue texts and notes) |
262
+ | `list_languages` | **Deprecated:** use `get_current_user` (field `language`) instead — will be removed in 2.0.0 |
196
263
  | `get_config` | Show server configuration (base URL, cache TTL) |
197
- | `get_issue_enums` | Return valid ID/name pairs for all issue enum fields (severity, status, priority, resolution, reproducibility) — use before `create_issue` / `update_issue` to look up correct values; on localized installations each entry may include a `canonical_name` with the standard English API name |
198
- | `get_mantis_version` | Get MantisBT version and check for updates |
199
- | `get_mcp_version` | Return the version of this mantisbt-mcp-server instance |
264
+ | `get_mantis_version` | **Deprecated:** use `get_version` instead — will be removed in 2.0.0 |
265
+ | `get_mcp_version` | **Deprecated:** use `get_version` instead — will be removed in 2.0.0 |
266
+
267
+ ## Token usage
268
+
269
+ The server handles counting and field projection itself — the model receives a compact summary instead of reading raw issue lists. Measured on 2026-10-31 against a MantisBT 2.28 installation (604 issues, five issues with long descriptions/notes):
270
+
271
+ | Task | Before | Tokens | After | Tokens | Saved |
272
+ |---|---|---:|---|---:|---:|
273
+ | Status overview of a project (604 issues) | `list_issues`, all pages | 1,274,398 | `list_issues` + `group_by: "status"` | 165 | 99.9 % |
274
+ | Open issues per handler | `list_issues` status `"open"` + `select`, all pages ¹ | 2,686 | `list_issues` + `group_by: "handler"` | 108 | 95.9 % |
275
+ | 5 issues with selected fields | `get_issue` ids, no `select` | 16,272 | `get_issue` ids + `select` | 623 | 96.1 % |
276
+
277
+ ¹ `list_issues` scans at most 500 issues when client-side filters are active, so this "before" value is understated.
278
+
279
+ Tokens estimated as characters / 4. To reproduce against your own instance (requires a git checkout, not available via npx):
280
+
281
+ ```bash
282
+ MANTIS_BASE_URL=… MANTIS_API_KEY=… \
283
+ BENCHMARK_PROJECT_ID=12 BENCHMARK_ISSUE_IDS=101,102,103,104,105 \
284
+ npm run benchmark
285
+ ```
286
+
287
+ Credentials may also come from `.env.local`.
200
288
 
201
289
  ## Available resources
202
290
 
@@ -205,7 +293,7 @@ MCP Resources are URI-addressable, read-only data that clients can fetch directl
205
293
  | Resource URI | Description |
206
294
  |---|---|
207
295
  | `mantis://me` | Profile of the authenticated API user (live fetch) |
208
- | `mantis://projects` | All accessible MantisBT projects as a compact list (cache-backed, refreshed via `sync_metadata`) |
296
+ | `mantis://projects` | All accessible MantisBT projects as a compact list (cache-backed, refreshed via `get_metadata` with `refresh: true`) |
209
297
  | `mantis://projects/{id}` | Combined project view: project fields + users + versions + categories in one call; cache-first, list-support for enumerating all available project URIs |
210
298
  | `mantis://enums` | Valid values for all issue enum fields: severity, priority, status, resolution, reproducibility (live fetch) |
211
299
 
@@ -252,6 +340,7 @@ npm run dev # Watch mode for development
252
340
  npm test # Run tests (vitest)
253
341
  npm run test:watch # Run tests in watch mode
254
342
  npm run test:coverage # Coverage report
343
+ npm run benchmark # Token benchmark against a live MantisBT (see Token usage above)
255
344
  ```
256
345
 
257
346
  ## License
package/dist/client.js CHANGED
@@ -38,6 +38,10 @@ export function buildIssueViewUrl(baseUrl, issueId) {
38
38
  export function buildNoteViewUrl(baseUrl, issueId, noteId) {
39
39
  return `${baseUrl}/view.php?id=${issueId}#bugnote${noteId}`;
40
40
  }
41
+ /** MantisBT sends download_url for attachments only via SOAP, so the REST tools build it themselves. */
42
+ export function buildAttachmentDownloadUrl(baseUrl, fileId) {
43
+ return `${baseUrl}/file_download.php?file_id=${fileId}&type=bug`;
44
+ }
41
45
  // ---------------------------------------------------------------------------
42
46
  // MantisApiError
43
47
  // ---------------------------------------------------------------------------
package/dist/coerce.js ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Turns the strings "true"/"false" into booleans before zod validation. Some
3
+ * MCP clients send boolean tool arguments as strings. Anything else is passed
4
+ * through unchanged, so zod still rejects invalid input.
5
+ *
6
+ * Use as z.preprocess(coerceBool, z.boolean()…).
7
+ */
8
+ export const coerceBool = (val) => val === 'true' ? true : val === 'false' ? false : val;
package/dist/config.js CHANGED
@@ -3,6 +3,7 @@ import { homedir } from 'node:os';
3
3
  import { join, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
5
  import { normalizeBaseUrl, usesIndexPhpEntryPoint } from './client.js';
6
+ import { parseToolsets } from './toolsets.js';
6
7
  // ---------------------------------------------------------------------------
7
8
  // .env.local loader
8
9
  // ---------------------------------------------------------------------------
@@ -70,6 +71,8 @@ function readNonCredentialConfig() {
70
71
  numThreads: searchNumThreads,
71
72
  },
72
73
  testEnvironment: process.env.MCP_TEST_ENVIRONMENT === 'true',
74
+ toolsets: parseToolsets(process.env.MANTIS_TOOLSETS),
75
+ disableDestructive: process.env.MANTIS_DISABLE_DESTRUCTIVE === 'true',
73
76
  };
74
77
  }
75
78
  /**
@@ -0,0 +1,9 @@
1
+ /** Major version in which deprecated tools are removed (see versioning rules in CLAUDE.md). */
2
+ export const REMOVAL_VERSION = '2.0.0';
3
+ /**
4
+ * First line of the description of a deprecated tool. Keeping the wording in
5
+ * one place keeps README, CHANGELOG and tests consistent.
6
+ */
7
+ export function deprecationNote(tool, replacement) {
8
+ return `Deprecated: use ${replacement} instead — ${tool} will be removed in ${REMOVAL_VERSION}.`;
9
+ }