@dpesch/mantisbt-mcp-server 1.12.0 → 1.13.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 +19 -0
- package/README.de.md +25 -21
- package/README.md +5 -1
- package/dist/cache.js +18 -0
- package/dist/index.js +2 -0
- package/dist/tools/project-versions.js +207 -0
- package/dist/tools/projects.js +1 -1
- package/docs/cookbook.de.md +306 -141
- package/docs/cookbook.md +165 -0
- package/docs/examples.de.md +23 -11
- package/docs/examples.md +12 -0
- package/package.json +1 -1
- package/server.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,25 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
|
+
## [1.13.0] – 2026-09-27
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- `create_version` — create a new version in a MantisBT project; returns the created version object (id, name, description, released, obsolete, timestamp)
|
|
19
|
+
- `update_version` — update an existing version; only passed fields change (at least one required); renaming rewrites version/target_version/fixed_in_version on all referencing issues; requires `manage_project_threshold` (default: manager)
|
|
20
|
+
- `release_version` — mark a version as released (sets its date, default now) and optionally create a follow-up version in the same step via `next_version`; requires `manage_project_threshold` (default: manager)
|
|
21
|
+
- `delete_version` — permanently delete a version; irreversible — MantisBT clears version/target_version/fixed_in_version on all referencing issues; requires `manage_project_threshold` (default: manager)
|
|
22
|
+
- Version writes are mirrored into the local metadata cache without resetting its timestamp, so `get_metadata` and the project resources stay current without extending the cache TTL.
|
|
23
|
+
- New cookbook recipes (EN/DE) for creating, updating, releasing and deleting versions, plus matching prompt examples in `docs/examples.md` and `docs/examples.de.md`.
|
|
24
|
+
|
|
25
|
+
### Changed
|
|
26
|
+
|
|
27
|
+
- `get_project_versions` description now documents the `timestamp` field returned per version and cross-references the new version write tools (`create_version`, `update_version`, `release_version`, `delete_version`)
|
|
28
|
+
- German documentation (`README.de.md`, `docs/cookbook.de.md`, `docs/examples.de.md`) now uses the en dash (–) as Gedankenstrich instead of the em dash.
|
|
29
|
+
- CI: the release job checks the npm token before publishing, and failing Codeberg/GitHub release API calls now fail the job instead of passing silently.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
14
33
|
## [1.12.0] – 2026-08-28
|
|
15
34
|
|
|
16
35
|
### Added
|
package/README.de.md
CHANGED
|
@@ -68,21 +68,21 @@ npm run build
|
|
|
68
68
|
|
|
69
69
|
| Variable | Pflicht | Standard | Beschreibung |
|
|
70
70
|
|---|---|---|---|
|
|
71
|
-
| `MANTIS_BASE_URL` | ✅ | – | Basis-URL der MantisBT-Installation. Beide Formate werden akzeptiert: `https://deine-mantis-instanz.example.com` und `https://deine-mantis-instanz.example.com/api/rest`
|
|
71
|
+
| `MANTIS_BASE_URL` | ✅ | – | Basis-URL der MantisBT-Installation. Beide Formate werden akzeptiert: `https://deine-mantis-instanz.example.com` und `https://deine-mantis-instanz.example.com/api/rest` – das `/api/rest`-Suffix wird automatisch normalisiert. |
|
|
72
72
|
| `MANTIS_API_KEY` | ✅ | – | API-Token für die Authentifizierung |
|
|
73
|
-
| `MANTIS_USE_INDEX_PHP` | – | auto | Auf `true` setzen, wenn URL-Rewriting nicht verfügbar ist
|
|
73
|
+
| `MANTIS_USE_INDEX_PHP` | – | auto | Auf `true` setzen, wenn URL-Rewriting nicht verfügbar ist – REST-Anfragen verwenden dann `/api/rest/index.php/` statt `/api/rest/`. Wird automatisch erkannt, wenn `MANTIS_BASE_URL` auf `/api/rest/index.php` endet; ein explizit gesetzter Wert hat immer Vorrang. Siehe [Cookbook](docs/cookbook.de.md#installation-ohne-url-rewriting-anbinden). |
|
|
74
74
|
| `MANTIS_CACHE_DIR` | – | `~/.cache/mantisbt-mcp` | Verzeichnis für den Metadaten-Cache |
|
|
75
75
|
| `MANTIS_CACHE_TTL` | – | `3600` | Cache-Lebensdauer in Sekunden |
|
|
76
76
|
| `TRANSPORT` | – | `stdio` | Transport-Modus: `stdio` oder `http` |
|
|
77
77
|
| `PORT` | – | `3000` | Port für HTTP-Modus |
|
|
78
|
-
| `MCP_HTTP_HOST` | – | `127.0.0.1` | Bind-Adresse für HTTP-Modus. **Geändert von `0.0.0.0` auf `127.0.0.1`**
|
|
79
|
-
| `MCP_HTTP_TOKEN` | ✅ (HTTP-Modus) | – | Bearer-Token für den `/mcp`-Endpunkt (`Authorization: Bearer <token>`). **Pflicht bei `TRANSPORT=http`**
|
|
78
|
+
| `MCP_HTTP_HOST` | – | `127.0.0.1` | Bind-Adresse für HTTP-Modus. **Geändert von `0.0.0.0` auf `127.0.0.1`** – der Server horcht standardmäßig nur auf localhost. Für Docker oder Remote-Zugriff `0.0.0.0` setzen. |
|
|
79
|
+
| `MCP_HTTP_TOKEN` | ✅ (HTTP-Modus) | – | Bearer-Token für den `/mcp`-Endpunkt (`Authorization: Bearer <token>`). **Pflicht bei `TRANSPORT=http`** – der Server startet im HTTP-Modus ohne diesen Wert nicht, damit Tools nie unauthentifiziert erreichbar sind. Im stdio-Modus ohne Bedeutung. `/health` ist immer öffentlich. |
|
|
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
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
|
-
| `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**
|
|
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
86
|
|
|
87
87
|
## Verfügbare Tools
|
|
88
88
|
|
|
@@ -92,9 +92,9 @@ npm run build
|
|
|
92
92
|
|---|---|
|
|
93
93
|
| `get_issue` | Ein Issue anhand seiner ID abrufen; optionales `select` zur Feldauswahl reduziert die Antwortgröße |
|
|
94
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
|
|
96
|
-
| `create_issue` | Neues Issue anlegen; `severity` und `priority` müssen kanonische englische Namen sein (z.B. `minor`, `major`, `normal`, `high`)
|
|
97
|
-
| `update_issue` | Bestehendes Issue bearbeiten; Enum-Felder (`status`, `priority`, `severity`, `resolution`, `reproducibility`) akzeptieren kanonische englische Namen, lokalisierte Namen oder numerische IDs
|
|
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) |
|
|
98
98
|
| `delete_issue` | Issue löschen |
|
|
99
99
|
|
|
100
100
|
### Notizen
|
|
@@ -139,20 +139,24 @@ npm run build
|
|
|
139
139
|
| Tool | Beschreibung |
|
|
140
140
|
|---|---|
|
|
141
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 |
|
|
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` |
|
|
143
|
+
| `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) |
|
|
146
|
+
| `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) |
|
|
143
147
|
| `get_project_categories` | Kategorien eines Projekts abrufen |
|
|
144
148
|
| `get_project_users` | Benutzer eines Projekts abrufen |
|
|
145
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 |
|
|
146
150
|
|
|
147
151
|
### Semantische Suche *(optional)*
|
|
148
152
|
|
|
149
|
-
Statt einfachem Keyword-Matching versteht die semantische Suche die *Bedeutung* einer Anfrage. Formuliere in natürlicher Sprache
|
|
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:
|
|
150
154
|
|
|
151
|
-
- *„Login funktioniert nach Passwort-Reset nicht"*
|
|
152
|
-
- *„Performance-Probleme auf der Checkout-Seite"*
|
|
153
|
-
- *„doppelte Einträge in der Rechnungsliste"*
|
|
155
|
+
- *„Login funktioniert nach Passwort-Reset nicht"* – findet Issues rund um Authentifizierungsgrenzfälle
|
|
156
|
+
- *„Performance-Probleme auf der Checkout-Seite"* – liefert verwandte Meldungen unabhängig von der verwendeten Terminologie
|
|
157
|
+
- *„doppelte Einträge in der Rechnungsliste"* – erkennt auch Issues, die als „zweifach angezeigt", „dupliziert" o.ä. beschrieben sind
|
|
154
158
|
|
|
155
|
-
Das Embedding-Modell (~80 MB) läuft vollständig **offline**
|
|
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).
|
|
156
160
|
|
|
157
161
|
Aktivierung mit `MANTIS_SEARCH_ENABLED=true`.
|
|
158
162
|
|
|
@@ -190,13 +194,13 @@ npm install sqlite-vec better-sqlite3
|
|
|
190
194
|
| `get_current_user` | Eigenes Benutzerprofil abrufen |
|
|
191
195
|
| `list_languages` | Verfügbare Sprachen auflisten |
|
|
192
196
|
| `get_config` | Server-Konfiguration (Basis-URL, Cache-TTL) anzeigen |
|
|
193
|
-
| `get_issue_enums` | Gültige ID/Name-Paare für alle Enum-Felder zurückgeben (Severity, Status, Priority, Resolution, Reproducibility)
|
|
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 |
|
|
194
198
|
| `get_mantis_version` | MantisBT-Version abrufen und auf Updates prüfen |
|
|
195
199
|
| `get_mcp_version` | Version dieser mantisbt-mcp-server-Instanz zurückgeben |
|
|
196
200
|
|
|
197
201
|
## Verfügbare Ressourcen
|
|
198
202
|
|
|
199
|
-
MCP-Ressourcen sind URI-adressierbare, schreibgeschützte Daten, die Clients direkt abrufen können, ohne ein Tool aufzurufen. Sie sind das dritte MCP-Primitiv neben Tools und Prompts. Hinweis: Ressourcen werden von MCP-Clients weniger breit unterstützt als Tools
|
|
203
|
+
MCP-Ressourcen sind URI-adressierbare, schreibgeschützte Daten, die Clients direkt abrufen können, ohne ein Tool aufzurufen. Sie sind das dritte MCP-Primitiv neben Tools und Prompts. Hinweis: Ressourcen werden von MCP-Clients weniger breit unterstützt als Tools – bitte die Dokumentation des jeweiligen Clients prüfen.
|
|
200
204
|
|
|
201
205
|
| Ressource-URI | Beschreibung |
|
|
202
206
|
|---|---|
|
|
@@ -207,7 +211,7 @@ MCP-Ressourcen sind URI-adressierbare, schreibgeschützte Daten, die Clients dir
|
|
|
207
211
|
|
|
208
212
|
## Verfügbare Prompts
|
|
209
213
|
|
|
210
|
-
MCP-Prompt-Templates sind Gesprächseinstiege, die den LLM anweisen, strukturierte Eingaben zu sammeln und dann das passende Tool aufzurufen. Es handelt sich nicht um Tools
|
|
214
|
+
MCP-Prompt-Templates sind Gesprächseinstiege, die den LLM anweisen, strukturierte Eingaben zu sammeln und dann das passende Tool aufzurufen. Es handelt sich nicht um Tools – sie starten einen geführten Arbeitsablauf.
|
|
211
215
|
|
|
212
216
|
| Prompt | Pflichtargumente | Optionale Argumente | Beschreibung |
|
|
213
217
|
|---|---|---|---|
|
|
@@ -218,7 +222,7 @@ MCP-Prompt-Templates sind Gesprächseinstiege, die den LLM anweisen, strukturier
|
|
|
218
222
|
|
|
219
223
|
## HTTP-Modus
|
|
220
224
|
|
|
221
|
-
Für den Einsatz als eigenständiger Server (z.B. in Remote-Setups). `MCP_HTTP_TOKEN` ist im HTTP-Modus **Pflicht**
|
|
225
|
+
Für den Einsatz als eigenständiger Server (z.B. in Remote-Setups). `MCP_HTTP_TOKEN` ist im HTTP-Modus **Pflicht** – der Server startet ohne diesen Wert nicht:
|
|
222
226
|
|
|
223
227
|
```bash
|
|
224
228
|
MCP_HTTP_TOKEN=secret MANTIS_BASE_URL=... MANTIS_API_KEY=... \
|
|
@@ -229,14 +233,14 @@ MCP_HTTP_TOKEN=secret MANTIS_BASE_URL=... MANTIS_API_KEY=... \
|
|
|
229
233
|
# TRANSPORT=http PORT=3456 MCP_HTTP_HOST=0.0.0.0 node dist/index.js
|
|
230
234
|
```
|
|
231
235
|
|
|
232
|
-
Jede `/mcp`-Anfrage muss `Authorization: Bearer <token>` senden. Im HTTP-Modus ist der `file_path` von `upload_file` deaktiviert, sofern `MANTIS_UPLOAD_DIR` nicht gesetzt ist
|
|
236
|
+
Jede `/mcp`-Anfrage muss `Authorization: Bearer <token>` senden. Im HTTP-Modus ist der `file_path` von `upload_file` deaktiviert, sofern `MANTIS_UPLOAD_DIR` nicht gesetzt ist – stattdessen den Parameter `content` (Base64) verwenden.
|
|
233
237
|
|
|
234
238
|
Healthcheck: `GET http://localhost:3456/health` (immer öffentlich, kein Token erforderlich)
|
|
235
239
|
|
|
236
240
|
## Dokumentation
|
|
237
241
|
|
|
238
|
-
- [**Cookbook**](docs/cookbook.de.md)
|
|
239
|
-
- [**Anwendungsbeispiele**](docs/examples.de.md)
|
|
242
|
+
- [**Cookbook**](docs/cookbook.de.md) – Tool-orientierte Rezepte mit direkt verwendbaren Parameter-Beispielen für alle registrierten Tools
|
|
243
|
+
- [**Anwendungsbeispiele**](docs/examples.de.md) – Beispiele in natürlicher Sprache für alltägliche Anwendungsfälle (keine Tool-Namen erforderlich)
|
|
240
244
|
|
|
241
245
|
## Entwicklung
|
|
242
246
|
|
package/README.md
CHANGED
|
@@ -139,7 +139,11 @@ npm run build
|
|
|
139
139
|
| Tool | Description |
|
|
140
140
|
|---|---|
|
|
141
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 |
|
|
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 |
|
|
143
|
+
| `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) |
|
|
146
|
+
| `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) |
|
|
143
147
|
| `get_project_categories` | Get categories of a project |
|
|
144
148
|
| `get_project_users` | Get users of a project |
|
|
145
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 |
|
package/dist/cache.js
CHANGED
|
@@ -56,6 +56,24 @@ export class MetadataCache {
|
|
|
56
56
|
};
|
|
57
57
|
await writeFile(this.filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
58
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Applies `mutate` to the cached version list of every project and writes the
|
|
61
|
+
* result back with the original timestamp — a partial update must not extend
|
|
62
|
+
* the TTL of the remaining metadata. No-op when no cache file exists.
|
|
63
|
+
*/
|
|
64
|
+
async patchVersions(mutate) {
|
|
65
|
+
let file;
|
|
66
|
+
try {
|
|
67
|
+
file = JSON.parse(await readFile(this.filePath, 'utf-8'));
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const [projectId, meta] of Object.entries(file.data.byProject)) {
|
|
73
|
+
meta.versions = mutate(Number(projectId), meta.versions);
|
|
74
|
+
}
|
|
75
|
+
await writeFile(this.filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
76
|
+
}
|
|
59
77
|
async invalidate() {
|
|
60
78
|
try {
|
|
61
79
|
await unlink(this.filePath);
|
package/dist/index.js
CHANGED
|
@@ -16,6 +16,7 @@ import { registerFileTools } from './tools/files.js';
|
|
|
16
16
|
import { registerRelationshipTools } from './tools/relationships.js';
|
|
17
17
|
import { registerMonitorTools } from './tools/monitors.js';
|
|
18
18
|
import { registerProjectTools } from './tools/projects.js';
|
|
19
|
+
import { registerProjectVersionTools } from './tools/project-versions.js';
|
|
19
20
|
import { registerUserTools } from './tools/users.js';
|
|
20
21
|
import { registerFilterTools } from './tools/filters.js';
|
|
21
22
|
import { registerConfigTools } from './tools/config.js';
|
|
@@ -60,6 +61,7 @@ async function createMcpServer(transport) {
|
|
|
60
61
|
registerRelationshipTools(server, client);
|
|
61
62
|
registerMonitorTools(server, client);
|
|
62
63
|
registerProjectTools(server, client, cache);
|
|
64
|
+
registerProjectVersionTools(server, client, cache);
|
|
63
65
|
registerUserTools(server, client);
|
|
64
66
|
registerFilterTools(server, client);
|
|
65
67
|
registerConfigTools(server, client, cache);
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { getVersionHint } from '../version-hint.js';
|
|
3
|
+
function errorText(msg) {
|
|
4
|
+
const vh = getVersionHint();
|
|
5
|
+
vh?.triggerLatestVersionFetch();
|
|
6
|
+
const hint = vh?.getUpdateHint();
|
|
7
|
+
return hint ? `Error: ${msg}\n\n${hint}` : `Error: ${msg}`;
|
|
8
|
+
}
|
|
9
|
+
const coerceBool = (val) => val === 'true' ? true : val === 'false' ? false : val;
|
|
10
|
+
/** Drops undefined values so PATCH only touches the fields the caller set. */
|
|
11
|
+
function definedFields(fields) {
|
|
12
|
+
return Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined));
|
|
13
|
+
}
|
|
14
|
+
const PERMISSION_NOTE = 'Requires the manage_project_threshold access level in the project (MantisBT default: manager).';
|
|
15
|
+
const OWNING_PROJECT_NOTE = 'The version must belong to project_id itself: versions inherited from a parent project can only be changed via the parent\'s project_id (otherwise MantisBT answers "Version not found"). get_project_versions with inherit=false (the default) lists the project\'s own versions.';
|
|
16
|
+
const projectIdSchema = z.coerce.number().int().positive().describe('Numeric ID of the project the version belongs to — use list_projects to discover project IDs');
|
|
17
|
+
const versionIdSchema = z.coerce.number().int().positive().describe('Numeric version ID — use get_project_versions to discover version IDs');
|
|
18
|
+
const timestampSchema = z.string().min(1).describe('Version date as ISO 8601 string, e.g. "2026-09-27" or "2026-09-27T10:00:00+02:00". Determines the order in roadmap and changelog.');
|
|
19
|
+
const boolFlag = z.preprocess(coerceBool, z.boolean());
|
|
20
|
+
export function registerProjectVersionTools(server, client, cache) {
|
|
21
|
+
// A failing cache update must never turn a successful API write into an error.
|
|
22
|
+
async function patchCachedVersions(mutate) {
|
|
23
|
+
try {
|
|
24
|
+
await cache?.patchVersions(mutate);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
// Cache is a best-effort mirror; sync_metadata repairs it
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function createVersion(projectId, fields) {
|
|
31
|
+
const result = await client.post(`projects/${projectId}/versions`, definedFields(fields));
|
|
32
|
+
// Only the owning project gets the new entry — whether subprojects inherit it depends on server config
|
|
33
|
+
await patchCachedVersions((pid, versions) => (pid === projectId ? [...versions, result.version] : versions));
|
|
34
|
+
return result.version;
|
|
35
|
+
}
|
|
36
|
+
async function updateVersion(projectId, versionId, fields) {
|
|
37
|
+
const result = await client.patch(`projects/${projectId}/versions/${versionId}`, definedFields(fields));
|
|
38
|
+
// Version IDs are global, so inherited copies in subprojects are updated as well
|
|
39
|
+
await patchCachedVersions((_pid, versions) => versions.map((v) => (v.id === versionId ? result.version : v)));
|
|
40
|
+
return result.version;
|
|
41
|
+
}
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// create_version
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
server.registerTool('create_version', {
|
|
46
|
+
title: 'Create Version',
|
|
47
|
+
description: `Create a new version in a MantisBT project. Returns the created version object (id, name, description, released, obsolete, timestamp).
|
|
48
|
+
|
|
49
|
+
Version names must be unique within the project. Without timestamp, MantisBT uses the current date.
|
|
50
|
+
|
|
51
|
+
${PERMISSION_NOTE}`,
|
|
52
|
+
inputSchema: z.object({
|
|
53
|
+
project_id: projectIdSchema,
|
|
54
|
+
name: z.string().trim().min(1).describe('Version name, e.g. "1.2.0"'),
|
|
55
|
+
description: z.string().optional().describe('Optional version description'),
|
|
56
|
+
released: boolFlag.optional().describe('Mark the version as released. MantisBT default: false'),
|
|
57
|
+
obsolete: boolFlag.optional().describe('Mark the version as obsolete. MantisBT default: false'),
|
|
58
|
+
timestamp: timestampSchema.optional(),
|
|
59
|
+
}),
|
|
60
|
+
annotations: {
|
|
61
|
+
readOnlyHint: false,
|
|
62
|
+
destructiveHint: false,
|
|
63
|
+
idempotentHint: false,
|
|
64
|
+
},
|
|
65
|
+
}, async ({ project_id, ...fields }) => {
|
|
66
|
+
try {
|
|
67
|
+
const created = await createVersion(project_id, fields);
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: 'text', text: JSON.stringify(created, null, 2) }],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
catch (error) {
|
|
73
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
74
|
+
return { content: [{ type: 'text', text: errorText(msg) }], isError: true };
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
// update_version
|
|
79
|
+
// ---------------------------------------------------------------------------
|
|
80
|
+
server.registerTool('update_version', {
|
|
81
|
+
title: 'Update Version',
|
|
82
|
+
description: `Update an existing version of a MantisBT project. Only the fields you pass are changed. Returns the updated version object.
|
|
83
|
+
|
|
84
|
+
Renaming a version also rewrites the version, target_version and fixed_in_version fields of all issues that reference it (including subprojects if versions are inherited).
|
|
85
|
+
|
|
86
|
+
Setting released=true does not change the version date — pass timestamp as well, or use release_version, which does both.
|
|
87
|
+
|
|
88
|
+
${OWNING_PROJECT_NOTE}
|
|
89
|
+
|
|
90
|
+
${PERMISSION_NOTE}`,
|
|
91
|
+
inputSchema: z.object({
|
|
92
|
+
project_id: projectIdSchema,
|
|
93
|
+
version_id: versionIdSchema,
|
|
94
|
+
name: z.string().trim().min(1).optional().describe('New version name (must be unique within the project)'),
|
|
95
|
+
description: z.string().optional().describe('New version description'),
|
|
96
|
+
released: boolFlag.optional().describe('Released flag'),
|
|
97
|
+
obsolete: boolFlag.optional().describe('Obsolete flag'),
|
|
98
|
+
timestamp: timestampSchema.optional(),
|
|
99
|
+
}),
|
|
100
|
+
annotations: {
|
|
101
|
+
readOnlyHint: false,
|
|
102
|
+
destructiveHint: false,
|
|
103
|
+
idempotentHint: true,
|
|
104
|
+
},
|
|
105
|
+
}, async ({ project_id, version_id, ...fields }) => {
|
|
106
|
+
if (Object.keys(definedFields(fields)).length === 0) {
|
|
107
|
+
return {
|
|
108
|
+
content: [{ type: 'text', text: errorText('No fields to update — pass at least one of name, description, released, obsolete, timestamp.') }],
|
|
109
|
+
isError: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const updated = await updateVersion(project_id, version_id, fields);
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: 'text', text: JSON.stringify(updated, null, 2) }],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
120
|
+
return { content: [{ type: 'text', text: errorText(msg) }], isError: true };
|
|
121
|
+
}
|
|
122
|
+
});
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// release_version
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
server.registerTool('release_version', {
|
|
127
|
+
title: 'Release Version',
|
|
128
|
+
description: `Mark a version as released and set its date (default: now). Optionally create a follow-up version in the same step.
|
|
129
|
+
|
|
130
|
+
The follow-up version is only created when next_version is given; its name is used as-is — no automatic numbering.
|
|
131
|
+
|
|
132
|
+
Returns { released: <version> } plus either next_version: <version> or next_version_error: <message>. The two steps are separate API calls: if creating the follow-up version fails, the release itself stays in effect and the error is reported in next_version_error.
|
|
133
|
+
|
|
134
|
+
${OWNING_PROJECT_NOTE}
|
|
135
|
+
|
|
136
|
+
${PERMISSION_NOTE}`,
|
|
137
|
+
inputSchema: z.object({
|
|
138
|
+
project_id: projectIdSchema,
|
|
139
|
+
version_id: versionIdSchema,
|
|
140
|
+
timestamp: timestampSchema.optional().describe('Release date as ISO 8601 string. Default: now.'),
|
|
141
|
+
next_version: z.string().trim().min(1).optional().describe('Name of a follow-up version to create as unreleased placeholder, e.g. "1.2.1". Omit to only release.'),
|
|
142
|
+
}),
|
|
143
|
+
annotations: {
|
|
144
|
+
readOnlyHint: false,
|
|
145
|
+
destructiveHint: false,
|
|
146
|
+
idempotentHint: false,
|
|
147
|
+
},
|
|
148
|
+
}, async ({ project_id, version_id, timestamp, next_version }) => {
|
|
149
|
+
let released;
|
|
150
|
+
try {
|
|
151
|
+
released = await updateVersion(project_id, version_id, {
|
|
152
|
+
released: true,
|
|
153
|
+
timestamp: timestamp ?? new Date().toISOString(),
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
catch (error) {
|
|
157
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
158
|
+
return { content: [{ type: 'text', text: errorText(msg) }], isError: true };
|
|
159
|
+
}
|
|
160
|
+
const result = { released };
|
|
161
|
+
if (next_version !== undefined) {
|
|
162
|
+
try {
|
|
163
|
+
result.next_version = await createVersion(project_id, { name: next_version });
|
|
164
|
+
}
|
|
165
|
+
catch (error) {
|
|
166
|
+
result.next_version_error = error instanceof Error ? error.message : String(error);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
171
|
+
};
|
|
172
|
+
});
|
|
173
|
+
// ---------------------------------------------------------------------------
|
|
174
|
+
// delete_version
|
|
175
|
+
// ---------------------------------------------------------------------------
|
|
176
|
+
server.registerTool('delete_version', {
|
|
177
|
+
title: 'Delete Version',
|
|
178
|
+
description: `Permanently delete a version of a MantisBT project. This action is irreversible.
|
|
179
|
+
|
|
180
|
+
MantisBT clears the version, target_version and fixed_in_version fields of all issues that reference the deleted version (including subprojects if versions are inherited). To retire a version without touching issues, set obsolete=true via update_version instead.
|
|
181
|
+
|
|
182
|
+
${OWNING_PROJECT_NOTE}
|
|
183
|
+
|
|
184
|
+
${PERMISSION_NOTE}`,
|
|
185
|
+
inputSchema: z.object({
|
|
186
|
+
project_id: projectIdSchema,
|
|
187
|
+
version_id: versionIdSchema,
|
|
188
|
+
}),
|
|
189
|
+
annotations: {
|
|
190
|
+
readOnlyHint: false,
|
|
191
|
+
destructiveHint: true,
|
|
192
|
+
idempotentHint: true,
|
|
193
|
+
},
|
|
194
|
+
}, async ({ project_id, version_id }) => {
|
|
195
|
+
try {
|
|
196
|
+
await client.delete(`projects/${project_id}/versions/${version_id}`);
|
|
197
|
+
await patchCachedVersions((_pid, versions) => versions.filter((v) => v.id !== version_id));
|
|
198
|
+
return {
|
|
199
|
+
content: [{ type: 'text', text: `Version ${version_id} deleted successfully.` }],
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
catch (error) {
|
|
203
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
204
|
+
return { content: [{ type: 'text', text: errorText(msg) }], isError: true };
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
}
|
package/dist/tools/projects.js
CHANGED
|
@@ -76,7 +76,7 @@ Prerequisites: obtain project_id from list_projects.`,
|
|
|
76
76
|
// ---------------------------------------------------------------------------
|
|
77
77
|
server.registerTool('get_project_versions', {
|
|
78
78
|
title: 'Get Project Versions',
|
|
79
|
-
description: `List all versions defined for a MantisBT project. Returns an array of version objects, each containing id, name, released (boolean), obsolete (boolean), and
|
|
79
|
+
description: `List all versions defined for a MantisBT project. Returns an array of version objects, each containing id, name, released (boolean), obsolete (boolean), and timestamp (version date). To create, change, release or delete versions use create_version, update_version, release_version and delete_version.
|
|
80
80
|
|
|
81
81
|
Use the returned version names directly when creating or updating issues via create_issue and update_issue (version, target_version, fixed_in_version fields).
|
|
82
82
|
|