@dpesch/mantisbt-mcp-server 1.14.1 → 1.16.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,70 @@ This project adheres to [Semantic Versioning](https://semver.org/).
11
11
 
12
12
  ---
13
13
 
14
+ ## [1.16.0] – 2026-11-02
15
+
16
+ ### Added
17
+
18
+ - Search benchmark `npm run benchmark:search` (`scripts/benchmark-search.ts`): measures recall@10 of `search_issues` on an existing index for exact titles, first sentences, sentences from deep in long descriptions, rare identifiers and — from `BENCHMARK_PARAPHRASES` — hand-written paraphrases, ranked by vectors only and hybrid. Reads the index only; refuses one built with another embedding signature.
19
+
20
+ ### Changed
21
+
22
+ - Semantic search: `search_issues` now combines the semantic ranking with a keyword ranking (BM25 over summary and description, summary counted twice) by Reciprocal Rank Fusion. Embeddings blur exact terms; a query for an identifier that occurs in at most three issues found its issue among the top 10 in 26 % of the cases, now in 100 % (7,739 issues). Literal queries gain as well — exact titles 97 % → 99.8 %, first sentences 83 % → 99 %, sentences from deep in a long description 58 % → 97 % — while paraphrased queries stay level (38 % → 37 %, 60 queries). `score` is still the semantic similarity, but results are now ordered by the combined ranking, so a keyword match can precede a higher `score`. The keyword index is built in memory on the first search (about 0.1 s and 7 MB for 7,739 issues) and adds about 2 ms per search.
23
+ - Semantic search: a long description is now split into up to 8 chunks of the model's token limit, each embedded together with the summary, and an issue scores by its best chunk. Details deep in a long description become findable (measured without the keyword ranking above): a sentence from the middle of a long description found its issue among the top 10 in 58 % of the cases instead of 13 % (7,739 issues); exact titles 97 % (before 96 %), first sentences 83 % (before 84 %). The index grows to about 1.7 vectors per issue — `index.json` from 67 to 113 MB for 7,739 issues, held in memory by every server process with search enabled. Further chunks are stored in a new optional `chunks` field next to `vector`, so a server of an older version can still read the index.
24
+
25
+ ### Fixed
26
+
27
+ - Semantic search: `search_issues` often missed issues with a longer description, even when queried with their exact title. The default model is trained on 128 tokens, but the text was embedded with up to 512, so the vectors of long issues collapsed into one another and short issues won almost every query. Text is now cut at the model's training length. On an index of 7,930 issues, an exact-title query found the issue among the top 10 in 95 % of the cases instead of 47 %, a query with the first sentence of the description in 84 % instead of 38 %. Text beyond that limit goes into further chunks (see Changed).
28
+ - Semantic search: the index is rebuilt automatically when it was made with another embedding model or token limit, including the first start after this update. Before, changing `MANTIS_SEARCH_MODEL` kept the old vectors, which cannot be compared with the new ones, until a `rebuild_search_index` with `full: true`. The model and token limit are stored in `embedding_signature.txt` in the search directory. The rebuild runs in the background sync of the first server start and takes about 6 minutes for 7,700 issues with the default of 1 thread (see below); until it is done, `search_issues` finds only the issues indexed so far. It also drops entries of issues that were deleted or are no longer visible, which an incremental sync never removes.
29
+ - Semantic search: `MANTIS_SEARCH_THREADS` had no effect since it was introduced in 1.5.1 — the embedding model always used every CPU core. The thread options were passed in snake_case (`intra_op_num_threads`), which onnxruntime silently ignores; it expects `intraOpNumThreads`. The documented default of 1 thread now applies, as intended against CPU saturation on WSL and multi-core machines. Embedding is about three times slower with 1 thread than with all cores; a full rebuild of 7,700 issues takes about 6 minutes instead of 2.5. With `MANTIS_SEARCH_THREADS=4` the rebuild took 160 s instead of 377 s with 1 thread; 4 is a good value on a machine with 4 or more cores.
30
+
31
+ ---
32
+
33
+ ## [1.15.0] – 2026-10-31
34
+
35
+ ### Deprecated
36
+
37
+ - `add_monitor` — use `update_issue` with `add_monitors` instead; `add_monitor` will be removed in 2.0.0.
38
+ - `remove_monitor` — use `update_issue` with `remove_monitors` instead; `remove_monitor` will be removed in 2.0.0.
39
+ - `get_issues` — use `get_issue` with `ids` instead; `get_issues` will be removed in 2.0.0.
40
+ - `list_notes` — use `get_issue` with `select: "notes"` instead; `list_notes` will be removed in 2.0.0.
41
+ - `list_issue_files` — use `get_issue` with `select: "attachments"` instead; `list_issue_files` will be removed in 2.0.0.
42
+ - `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.
43
+ - `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.
44
+ - `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.
45
+ - `get_mcp_version`, `get_mantis_version` — use `get_version` instead; they will be removed in 2.0.0.
46
+ - `get_issue_fields`, `get_issue_enums` — use `get_issue_schema` instead; they will be removed in 2.0.0. The resource `mantis://enums` stays.
47
+ - `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.
48
+ - `release_version` — use `update_version` with `released: true`, `timestamp`, and optionally `next_version` instead; `release_version` will be removed in 2.0.0.
49
+
50
+ ### Added
51
+
52
+ - `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`.
53
+ - `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.
54
+ - `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`.
55
+ - 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.
56
+ - Attachments in `get_issue` and `list_issue_files` now carry a `download_url`.
57
+ - `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).
58
+ - 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.
59
+ - New tool `get_issue_schema` (`project_id` optional): the valid `select` fields and the enum values (severity, status, priority, resolution, reproducibility) in one response.
60
+ - `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`.
61
+ - `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.
62
+ - 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`).
63
+ - 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.
64
+ - 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.
65
+ - `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.
66
+ - `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).
67
+
68
+ ### Fixed
69
+
70
+ - 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.
71
+ - `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.
72
+ - `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.
73
+ - `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.
74
+ - `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.
75
+
76
+ ---
77
+
14
78
  ## [1.14.1] – 2026-10-24
15
79
 
16
80
  ### Fixed
package/README.de.md CHANGED
@@ -80,9 +80,72 @@ npm run build
80
80
  | `MANTIS_SEARCH_ENABLED` | – | `false` | Auf `true` setzen, um die semantische Suche zu aktivieren |
81
81
  | `MANTIS_SEARCH_BACKEND` | – | `vectra` | Vektorspeicher: `vectra` (reines JS) oder `sqlite-vec` (manuelle Installation erforderlich) |
82
82
  | `MANTIS_SEARCH_DIR` | – | `{MANTIS_CACHE_DIR}/search` | Verzeichnis für den Suchindex |
83
- | `MANTIS_SEARCH_MODEL` | – | `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | Embedding-Modell (wird beim ersten Start einmalig heruntergeladen, ~80 MB) |
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. |
83
+ | `MANTIS_SEARCH_MODEL` | – | `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | Embedding-Modell (wird beim ersten Start einmalig heruntergeladen, ~80 MB). Nach einem Wechsel baut der nächste Sync den Index von selbst neu auf. |
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. Auf Rechnern mit 4 oder mehr Kernen ist `4` ein guter Wert: Ein vollständiger Neuaufbau von 7.700 Issues dauerte damit 160 s statt 377 s mit 1 Thread. Die Einstellung beschleunigt vor allem das Indexieren; eine Suchanfrage ist ein einzelner kurzer Text und profitiert kaum. Wirksam ab 1.16.0 – frühere Versionen haben die Einstellung ignoriert und immer alle Kerne genutzt. |
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,33 +201,34 @@ 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
 
153
- Statt einfachem Keyword-Matching versteht die semantische Suche die *Bedeutung* einer Anfrage. Formuliere in natürlicher Sprache – die Suche findet konzeptuell verwandte Issues, auch wenn die genauen Begriffe nicht übereinstimmen:
217
+ Die semantische Suche versteht die *Bedeutung* einer Anfrage, nicht nur ihre Stichwörter. Formuliere in natürlicher Sprache – die Suche findet konzeptuell verwandte Issues, auch wenn die genauen Begriffe nicht übereinstimmen:
154
218
 
155
219
  - *„Login funktioniert nach Passwort-Reset nicht"* – findet Issues rund um Authentifizierungsgrenzfälle
156
220
  - *„Performance-Probleme auf der Checkout-Seite"* – liefert verwandte Meldungen unabhängig von der verwendeten Terminologie
157
221
  - *„doppelte Einträge in der Rechnungsliste"* – erkennt auch Issues, die als „zweifach angezeigt", „dupliziert" o.ä. beschrieben sind
158
222
 
159
- Das Embedding-Modell (~80 MB) läuft vollständig **offline** – kein OpenAI-Key, keine externe API. Es wird beim ersten Start einmalig heruntergeladen und lokal gecacht. Issues werden bei jedem Serverstart inkrementell indexiert (nur neue und geänderte Issues werden neu verarbeitet). Starten mehrere Client-Sitzungen gleichzeitig einen Server, synchronisiert nur einer davon den gemeinsamen Index. Das Modell wird bei der ersten Nutzung geladen und nach 10 Minuten ohne Einbetten wieder entladen, damit ruhende Sitzungen seinen Speicher (~0,9 GB pro Serverprozess) nicht dauerhaft belegen.
223
+ Das Embedding-Modell (~80 MB) läuft vollständig **offline** – kein OpenAI-Key, keine externe API. Es wird beim ersten Start einmalig heruntergeladen und lokal gecacht. Issues werden bei jedem Serverstart inkrementell indexiert (nur neue und geänderte Issues werden neu verarbeitet). Starten mehrere Client-Sitzungen gleichzeitig einen Server, synchronisiert nur einer davon den gemeinsamen Index. Das Modell wird bei der ersten Nutzung geladen und nach 10 Minuten ohne Einbetten wieder entladen, damit ruhende Sitzungen seinen Speicher (~0,9 GB pro Serverprozess) nicht dauerhaft belegen. Lange Beschreibungen werden in bis zu 8 Abschnitte geteilt, damit auch Details weiter unten gefunden werden; der Index enthält dadurch etwa 1,7 Vektoren pro Issue (rund 115 MB bei 7.700 Issues), die jeder Serverprozess mit aktivierter Suche im Speicher hält. Die Rangfolge berücksichtigt zusätzlich exakte Stichworttreffer, sodass eine Suche nach einem Bezeichner, Fehlercode oder einer Versionsnummer die Issues findet, die ihn enthalten.
160
224
 
161
225
  Aktivierung mit `MANTIS_SEARCH_ENABLED=true`.
162
226
 
163
227
  | Tool | Beschreibung |
164
228
  |---|---|
165
- | `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) |
229
+ | `search_issues` | Natürlichsprachige Suche über alle indizierten Issues – kombiniert semantische Ähnlichkeit mit Stichwortsuche (BM25), damit auch exakte Begriffe wie Bezeichner oder Fehlercodes gefunden werden; liefert Top-N-Ergebnisse mit der Cosine-Similarity als `score`, sortiert nach der kombinierten Rangfolge; 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,8 @@ 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)
344
+ npm run benchmark:search # Such-Trefferquote auf einem bestehenden Index (MANTIS_SEARCH_DIR), Vektor vs. hybrid
255
345
  ```
256
346
 
257
347
  ## Lizenz
package/README.md CHANGED
@@ -80,9 +80,72 @@ npm run build
80
80
  | `MANTIS_SEARCH_ENABLED` | – | `false` | Set to `true` to enable semantic search |
81
81
  | `MANTIS_SEARCH_BACKEND` | – | `vectra` | Vector store backend: `vectra` (pure JS) or `sqlite-vec` (requires manual install) |
82
82
  | `MANTIS_SEARCH_DIR` | – | `{MANTIS_CACHE_DIR}/search` | Directory for the search index |
83
- | `MANTIS_SEARCH_MODEL` | – | `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | Embedding model name (downloaded once on first use, ~80 MB) |
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. |
83
+ | `MANTIS_SEARCH_MODEL` | – | `Xenova/paraphrase-multilingual-MiniLM-L12-v2` | Embedding model name (downloaded once on first use, ~80 MB). After a change the index is rebuilt automatically on the next sync. |
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. On a machine with 4 or more cores, `4` is a good value: a full rebuild of 7,700 issues took 160 s instead of 377 s with 1 thread. The setting mainly speeds up indexing; a search query is a single short text and hardly benefits. Effective since 1.16.0 — earlier versions ignored this setting and always used every core. |
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,33 +201,34 @@ 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
 
153
- Instead of exact keyword matching, semantic search understands the *meaning* behind a query. Ask in plain language — the search engine finds conceptually related issues even when the wording doesn't match:
217
+ Semantic search understands the *meaning* behind a query, not just its keywords. Ask in plain language — the search engine finds conceptually related issues even when the wording doesn't match:
154
218
 
155
219
  - *"login fails after password reset"* — finds issues about authentication edge cases
156
220
  - *"performance problems on the checkout page"* — surfaces related reports regardless of the exact terminology used
157
221
  - *"duplicate entries in the invoice list"* — catches issues described as "shown twice", "double records", etc.
158
222
 
159
- The embedding model (~80 MB) runs entirely **offline** — no OpenAI key, no external API. It is downloaded once on first start and cached locally. Issues are indexed incrementally on every server start (only new and updated issues are re-indexed). When several client sessions start a server at the same time, only one of them syncs the shared index. The model is loaded on first use and unloaded again after 10 minutes without embedding, so idle sessions do not hold on to its memory (~0.9 GB per server process).
223
+ The embedding model (~80 MB) runs entirely **offline** — no OpenAI key, no external API. It is downloaded once on first start and cached locally. Issues are indexed incrementally on every server start (only new and updated issues are re-indexed). When several client sessions start a server at the same time, only one of them syncs the shared index. The model is loaded on first use and unloaded again after 10 minutes without embedding, so idle sessions do not hold on to its memory (~0.9 GB per server process). Long descriptions are split into up to 8 chunks so that details further down are found as well; the index then holds about 1.7 vectors per issue (around 115 MB for 7,700 issues), which every server process with search enabled keeps in memory. The ranking also takes exact keyword matches into account, so a query for an identifier, error code or version number finds the issues that contain it.
160
224
 
161
225
  Activate with `MANTIS_SEARCH_ENABLED=true`.
162
226
 
163
227
  | Tool | Description |
164
228
  |---|---|
165
- | `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**`) |
229
+ | `search_issues` | Natural language search over all indexed issues — combines semantic similarity with keyword matching (BM25), so exact terms such as identifiers or error codes are found as well; returns top-N results with the cosine similarity as `score`, ordered by the combined ranking; 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,8 @@ 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)
344
+ npm run benchmark:search # Search recall on an existing index (MANTIS_SEARCH_DIR), vectors vs. hybrid
255
345
  ```
256
346
 
257
347
  ## License