@dpesch/mantisbt-mcp-server 1.0.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/.env.local +2 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.de.md +177 -0
- package/README.md +177 -0
- package/dist/cache.js +52 -0
- package/dist/client.js +114 -0
- package/dist/config.js +54 -0
- package/dist/constants.js +23 -0
- package/dist/index.js +120 -0
- package/dist/tools/config.js +107 -0
- package/dist/tools/files.js +37 -0
- package/dist/tools/filters.js +35 -0
- package/dist/tools/issues.js +191 -0
- package/dist/tools/metadata.js +119 -0
- package/dist/tools/monitors.js +38 -0
- package/dist/tools/notes.js +96 -0
- package/dist/tools/projects.js +127 -0
- package/dist/tools/relationships.js +54 -0
- package/dist/tools/tags.js +78 -0
- package/dist/tools/users.js +34 -0
- package/dist/tools/version.js +58 -0
- package/dist/types.js +4 -0
- package/dist/version-hint.js +117 -0
- package/package.json +41 -0
- package/scripts/record-fixtures.ts +138 -0
- package/tests/cache.test.ts +149 -0
- package/tests/client.test.ts +241 -0
- package/tests/config.test.ts +164 -0
- package/tests/fixtures/get_current_user.json +39 -0
- package/tests/fixtures/get_issue.json +151 -0
- package/tests/fixtures/get_project_categories.json +60 -0
- package/tests/fixtures/get_project_versions.json +3 -0
- package/tests/fixtures/get_project_versions_with_data.json +28 -0
- package/tests/fixtures/list_issues.json +67 -0
- package/tests/fixtures/list_projects.json +65 -0
- package/tests/fixtures/recorded/get_current_user.json +108 -0
- package/tests/fixtures/recorded/get_issue.json +320 -0
- package/tests/fixtures/recorded/get_project_categories.json +241 -0
- package/tests/fixtures/recorded/get_project_versions.json +3 -0
- package/tests/fixtures/recorded/list_issues.json +824 -0
- package/tests/fixtures/recorded/list_projects.json +10641 -0
- package/tests/helpers/mock-server.ts +32 -0
- package/tests/tools/issues.test.ts +130 -0
- package/tests/tools/projects.test.ts +169 -0
- package/tests/tools/users.test.ts +76 -0
- package/tests/version-hint.test.ts +230 -0
- package/tsconfig.build.json +8 -0
- package/vitest.config.ts +8 -0
package/.env.local
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
|
6
|
+
This project adheres to [Semantic Versioning](https://semver.org/).
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## [1.0.0] – 2026-03-15
|
|
11
|
+
|
|
12
|
+
First stable release.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
**Issues**
|
|
17
|
+
- `get_issue` — retrieve an issue by numeric ID
|
|
18
|
+
- `list_issues` — filter issues by project, status, author, page, and page size
|
|
19
|
+
- `create_issue` — create a new issue
|
|
20
|
+
- `update_issue` — update an existing issue
|
|
21
|
+
- `delete_issue` — delete an issue
|
|
22
|
+
|
|
23
|
+
**Notes**
|
|
24
|
+
- `list_notes` — list notes of an issue
|
|
25
|
+
- `add_note` — add a note to an issue
|
|
26
|
+
- `delete_note` — delete a note
|
|
27
|
+
|
|
28
|
+
**Attachments**
|
|
29
|
+
- `list_issue_files` — list attachments of an issue
|
|
30
|
+
|
|
31
|
+
**Relationships**
|
|
32
|
+
- `add_relationship` — create a relationship between two issues
|
|
33
|
+
|
|
34
|
+
**Monitors**
|
|
35
|
+
- `add_monitor` — add yourself as a monitor of an issue
|
|
36
|
+
|
|
37
|
+
**Tags**
|
|
38
|
+
- `list_tags` — list all available tags
|
|
39
|
+
- `attach_tags` — attach tags to an issue
|
|
40
|
+
- `detach_tag` — remove a tag from an issue
|
|
41
|
+
|
|
42
|
+
**Projects**
|
|
43
|
+
- `list_projects` — list all accessible projects
|
|
44
|
+
- `get_project_versions` — get versions of a project
|
|
45
|
+
- `get_project_categories` — get categories of a project
|
|
46
|
+
- `get_project_users` — get users of a project
|
|
47
|
+
|
|
48
|
+
**Metadata & System**
|
|
49
|
+
- `get_metadata` — retrieve cached metadata (projects, users, versions, categories)
|
|
50
|
+
- `sync_metadata` — refresh the metadata cache
|
|
51
|
+
- `list_filters` — list saved filters
|
|
52
|
+
- `get_current_user` — retrieve your own user profile
|
|
53
|
+
- `list_languages` — list available languages
|
|
54
|
+
- `get_config` — show server configuration (base URL, cache TTL)
|
|
55
|
+
- `get_mantis_version` — get MantisBT version and check for updates on GitHub
|
|
56
|
+
|
|
57
|
+
**Infrastructure**
|
|
58
|
+
- stdio and HTTP transport (Streamable HTTP)
|
|
59
|
+
- Metadata cache with configurable TTL (default: 1 hour)
|
|
60
|
+
- Config file fallback (`~/.claude/mantis.json`)
|
|
61
|
+
- `VersionHintService`: appends update hint to API error messages
|
|
62
|
+
- Vitest test suite with 73 unit and fixture-based tests
|
|
63
|
+
- MIT license, `CONTRIBUTING.md`, `README.md`
|
|
64
|
+
|
|
65
|
+
### Fixed
|
|
66
|
+
- `get_project_categories` was calling `/projects/{id}/categories` (does not exist in MantisBT) — corrected to `GET projects/{id}` with extraction of `.projects[0].categories`
|
|
67
|
+
|
|
68
|
+
[1.0.0]: https://codeberg.org/dpesch/mantisbt-mcp-server/releases/tag/v1.0.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Dominik Pesch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.de.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# MantisBT MCP Server
|
|
2
|
+
|
|
3
|
+
Ein [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) Server, der die [MantisBT REST API](https://documenter.getpostman.com/view/29959/mantis-bug-tracker-rest-api) in Claude Code und andere MCP-fähige Clients integriert. Issues lesen, erstellen und bearbeiten – direkt aus dem Editor heraus.
|
|
4
|
+
|
|
5
|
+
## Voraussetzungen
|
|
6
|
+
|
|
7
|
+
- Node.js ≥ 18
|
|
8
|
+
- MantisBT-Installation mit aktivierter REST-API (ab Version 2.23)
|
|
9
|
+
- MantisBT API-Token (unter *Mein Konto → API-Token* erstellen)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
**Via npx (empfohlen):**
|
|
14
|
+
|
|
15
|
+
In `~/.claude/claude_desktop_config.json` (Claude Desktop) oder der lokalen
|
|
16
|
+
`claude_desktop_config.json` (Claude Code) eintragen:
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"mantisbt": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "@dpesch/mantisbt-mcp-server"],
|
|
24
|
+
"env": {
|
|
25
|
+
"MANTIS_BASE_URL": "https://deine-mantis-instanz.example.com/api/rest",
|
|
26
|
+
"MANTIS_API_KEY": "dein-api-token"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Lokaler Build:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://codeberg.org/dpesch/mantisbt-mcp-server
|
|
37
|
+
cd mantisbt-mcp-server
|
|
38
|
+
npm install
|
|
39
|
+
npm run build
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"mantisbt": {
|
|
46
|
+
"command": "node",
|
|
47
|
+
"args": ["/pfad/zum/mantisbt-mcp-server/dist/index.js"],
|
|
48
|
+
"env": {
|
|
49
|
+
"MANTIS_BASE_URL": "https://deine-mantis-instanz.example.com/api/rest",
|
|
50
|
+
"MANTIS_API_KEY": "dein-api-token"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Konfiguration
|
|
58
|
+
|
|
59
|
+
### Umgebungsvariablen
|
|
60
|
+
|
|
61
|
+
| Variable | Pflicht | Standard | Beschreibung |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `MANTIS_BASE_URL` | ✅ | – | Basis-URL der MantisBT REST API |
|
|
64
|
+
| `MANTIS_API_KEY` | ✅ | – | API-Token für die Authentifizierung |
|
|
65
|
+
| `MANTIS_CACHE_DIR` | – | `~/.cache/mantisbt-mcp` | Verzeichnis für den Metadaten-Cache |
|
|
66
|
+
| `MANTIS_CACHE_TTL` | – | `3600` | Cache-Lebensdauer in Sekunden |
|
|
67
|
+
| `TRANSPORT` | – | `stdio` | Transport-Modus: `stdio` oder `http` |
|
|
68
|
+
| `PORT` | – | `3000` | Port für HTTP-Modus |
|
|
69
|
+
|
|
70
|
+
### Config-Datei (Fallback)
|
|
71
|
+
|
|
72
|
+
Falls keine Umgebungsvariablen gesetzt sind, wird `~/.claude/mantis.json` ausgelesen:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"base_url": "https://deine-mantis-instanz.example.com/api/rest",
|
|
77
|
+
"api_key": "dein-api-token"
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Verfügbare Tools
|
|
82
|
+
|
|
83
|
+
### Issues
|
|
84
|
+
|
|
85
|
+
| Tool | Beschreibung |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `get_issue` | Ein Issue anhand seiner ID abrufen |
|
|
88
|
+
| `list_issues` | Issues nach Projekt, Status, Autor u.v.m. filtern |
|
|
89
|
+
| `create_issue` | Neues Issue anlegen |
|
|
90
|
+
| `update_issue` | Bestehendes Issue bearbeiten |
|
|
91
|
+
| `delete_issue` | Issue löschen |
|
|
92
|
+
|
|
93
|
+
### Notizen
|
|
94
|
+
|
|
95
|
+
| Tool | Beschreibung |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `list_notes` | Alle Notizen eines Issues auflisten |
|
|
98
|
+
| `add_note` | Notiz zu einem Issue hinzufügen |
|
|
99
|
+
| `delete_note` | Notiz löschen |
|
|
100
|
+
|
|
101
|
+
### Anhänge
|
|
102
|
+
|
|
103
|
+
| Tool | Beschreibung |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `list_issue_files` | Anhänge eines Issues auflisten |
|
|
106
|
+
|
|
107
|
+
### Beziehungen
|
|
108
|
+
|
|
109
|
+
| Tool | Beschreibung |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `add_relationship` | Beziehung zwischen zwei Issues erstellen |
|
|
112
|
+
|
|
113
|
+
### Beobachter
|
|
114
|
+
|
|
115
|
+
| Tool | Beschreibung |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `add_monitor` | Sich selbst als Beobachter eines Issues eintragen |
|
|
118
|
+
|
|
119
|
+
### Tags
|
|
120
|
+
|
|
121
|
+
| Tool | Beschreibung |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `list_tags` | Alle verfügbaren Tags auflisten |
|
|
124
|
+
| `attach_tags` | Tags an ein Issue hängen |
|
|
125
|
+
| `detach_tag` | Tag von einem Issue entfernen |
|
|
126
|
+
|
|
127
|
+
### Projekte
|
|
128
|
+
|
|
129
|
+
| Tool | Beschreibung |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `list_projects` | Alle zugänglichen Projekte auflisten |
|
|
132
|
+
| `get_project_versions` | Versionen eines Projekts abrufen |
|
|
133
|
+
| `get_project_categories` | Kategorien eines Projekts abrufen |
|
|
134
|
+
| `get_project_users` | Benutzer eines Projekts abrufen |
|
|
135
|
+
|
|
136
|
+
### Metadaten & System
|
|
137
|
+
|
|
138
|
+
| Tool | Beschreibung |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `get_metadata` | Gecachte Metadaten abrufen (Projekte, Benutzer, Versionen, Kategorien) |
|
|
141
|
+
| `sync_metadata` | Metadaten-Cache neu befüllen |
|
|
142
|
+
| `list_filters` | Gespeicherte Filter auflisten |
|
|
143
|
+
| `get_current_user` | Eigenes Benutzerprofil abrufen |
|
|
144
|
+
| `list_languages` | Verfügbare Sprachen auflisten |
|
|
145
|
+
| `get_config` | Server-Konfiguration (Basis-URL, Cache-TTL) anzeigen |
|
|
146
|
+
| `get_mantis_version` | MantisBT-Version abrufen und auf Updates prüfen |
|
|
147
|
+
|
|
148
|
+
## HTTP-Modus
|
|
149
|
+
|
|
150
|
+
Für den Einsatz als eigenständiger Server (z.B. in Remote-Setups):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
MANTIS_BASE_URL=... MANTIS_API_KEY=... TRANSPORT=http PORT=3456 node dist/index.js
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Healthcheck: `GET http://localhost:3456/health`
|
|
157
|
+
|
|
158
|
+
## Entwicklung
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm install # Abhängigkeiten installieren
|
|
162
|
+
npm run build # TypeScript → dist/ kompilieren
|
|
163
|
+
npm run typecheck # Typprüfung ohne Ausgabe
|
|
164
|
+
npm run dev # Watch-Modus für Entwicklung
|
|
165
|
+
npm test # Tests ausführen (vitest)
|
|
166
|
+
npm run test:watch # Tests im Watch-Modus
|
|
167
|
+
npm run test:coverage # Coverage-Report
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Lizenz
|
|
171
|
+
|
|
172
|
+
MIT – siehe [LICENSE](LICENSE)
|
|
173
|
+
|
|
174
|
+
## Mitwirken
|
|
175
|
+
|
|
176
|
+
Beiträge willkommen! Bitte [CONTRIBUTING.md](CONTRIBUTING.md) lesen.
|
|
177
|
+
Repository: [codeberg.org/dpesch/mantisbt-mcp-server](https://codeberg.org/dpesch/mantisbt-mcp-server)
|
package/README.md
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
# MantisBT MCP Server
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server that integrates the [MantisBT REST API](https://documenter.getpostman.com/view/29959/mantis-bug-tracker-rest-api) into Claude Code and other MCP-capable clients. Read, create, and update issues directly from your editor.
|
|
4
|
+
|
|
5
|
+
## Requirements
|
|
6
|
+
|
|
7
|
+
- Node.js ≥ 18
|
|
8
|
+
- MantisBT installation with REST API enabled (version 2.23+)
|
|
9
|
+
- MantisBT API token (create under *My Account → API Tokens*)
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
**Via npx (recommended):**
|
|
14
|
+
|
|
15
|
+
Add to `~/.claude/claude_desktop_config.json` (Claude Desktop) or your local
|
|
16
|
+
`claude_desktop_config.json` (Claude Code):
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"mantisbt": {
|
|
22
|
+
"command": "npx",
|
|
23
|
+
"args": ["-y", "@dpesch/mantisbt-mcp-server"],
|
|
24
|
+
"env": {
|
|
25
|
+
"MANTIS_BASE_URL": "https://your-mantis.example.com/api/rest",
|
|
26
|
+
"MANTIS_API_KEY": "your-api-token"
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Local build:**
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
git clone https://codeberg.org/dpesch/mantisbt-mcp-server
|
|
37
|
+
cd mantisbt-mcp-server
|
|
38
|
+
npm install
|
|
39
|
+
npm run build
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"mantisbt": {
|
|
46
|
+
"command": "node",
|
|
47
|
+
"args": ["/path/to/mantisbt-mcp-server/dist/index.js"],
|
|
48
|
+
"env": {
|
|
49
|
+
"MANTIS_BASE_URL": "https://your-mantis.example.com/api/rest",
|
|
50
|
+
"MANTIS_API_KEY": "your-api-token"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Configuration
|
|
58
|
+
|
|
59
|
+
### Environment variables
|
|
60
|
+
|
|
61
|
+
| Variable | Required | Default | Description |
|
|
62
|
+
|---|---|---|---|
|
|
63
|
+
| `MANTIS_BASE_URL` | ✅ | – | Base URL of the MantisBT REST API |
|
|
64
|
+
| `MANTIS_API_KEY` | ✅ | – | API token for authentication |
|
|
65
|
+
| `MANTIS_CACHE_DIR` | – | `~/.cache/mantisbt-mcp` | Directory for the metadata cache |
|
|
66
|
+
| `MANTIS_CACHE_TTL` | – | `3600` | Cache lifetime in seconds |
|
|
67
|
+
| `TRANSPORT` | – | `stdio` | Transport mode: `stdio` or `http` |
|
|
68
|
+
| `PORT` | – | `3000` | Port for HTTP mode |
|
|
69
|
+
|
|
70
|
+
### Config file (fallback)
|
|
71
|
+
|
|
72
|
+
If no environment variables are set, `~/.claude/mantis.json` is read:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"base_url": "https://your-mantis.example.com/api/rest",
|
|
77
|
+
"api_key": "your-api-token"
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Available tools
|
|
82
|
+
|
|
83
|
+
### Issues
|
|
84
|
+
|
|
85
|
+
| Tool | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `get_issue` | Retrieve an issue by its numeric ID |
|
|
88
|
+
| `list_issues` | Filter issues by project, status, author, and more |
|
|
89
|
+
| `create_issue` | Create a new issue |
|
|
90
|
+
| `update_issue` | Update an existing issue |
|
|
91
|
+
| `delete_issue` | Delete an issue |
|
|
92
|
+
|
|
93
|
+
### Notes
|
|
94
|
+
|
|
95
|
+
| Tool | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `list_notes` | List all notes of an issue |
|
|
98
|
+
| `add_note` | Add a note to an issue |
|
|
99
|
+
| `delete_note` | Delete a note |
|
|
100
|
+
|
|
101
|
+
### Attachments
|
|
102
|
+
|
|
103
|
+
| Tool | Description |
|
|
104
|
+
|---|---|
|
|
105
|
+
| `list_issue_files` | List attachments of an issue |
|
|
106
|
+
|
|
107
|
+
### Relationships
|
|
108
|
+
|
|
109
|
+
| Tool | Description |
|
|
110
|
+
|---|---|
|
|
111
|
+
| `add_relationship` | Create a relationship between two issues |
|
|
112
|
+
|
|
113
|
+
### Monitors
|
|
114
|
+
|
|
115
|
+
| Tool | Description |
|
|
116
|
+
|---|---|
|
|
117
|
+
| `add_monitor` | Add yourself as a monitor of an issue |
|
|
118
|
+
|
|
119
|
+
### Tags
|
|
120
|
+
|
|
121
|
+
| Tool | Description |
|
|
122
|
+
|---|---|
|
|
123
|
+
| `list_tags` | List all available tags |
|
|
124
|
+
| `attach_tags` | Attach tags to an issue |
|
|
125
|
+
| `detach_tag` | Remove a tag from an issue |
|
|
126
|
+
|
|
127
|
+
### Projects
|
|
128
|
+
|
|
129
|
+
| Tool | Description |
|
|
130
|
+
|---|---|
|
|
131
|
+
| `list_projects` | List all accessible projects |
|
|
132
|
+
| `get_project_versions` | Get versions of a project |
|
|
133
|
+
| `get_project_categories` | Get categories of a project |
|
|
134
|
+
| `get_project_users` | Get users of a project |
|
|
135
|
+
|
|
136
|
+
### Metadata & system
|
|
137
|
+
|
|
138
|
+
| Tool | Description |
|
|
139
|
+
|---|---|
|
|
140
|
+
| `get_metadata` | Retrieve cached metadata (projects, users, versions, categories) |
|
|
141
|
+
| `sync_metadata` | Refresh the metadata cache |
|
|
142
|
+
| `list_filters` | List saved filters |
|
|
143
|
+
| `get_current_user` | Retrieve your own user profile |
|
|
144
|
+
| `list_languages` | List available languages |
|
|
145
|
+
| `get_config` | Show server configuration (base URL, cache TTL) |
|
|
146
|
+
| `get_mantis_version` | Get MantisBT version and check for updates |
|
|
147
|
+
|
|
148
|
+
## HTTP mode
|
|
149
|
+
|
|
150
|
+
For use as a standalone server (e.g. in remote setups):
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
MANTIS_BASE_URL=... MANTIS_API_KEY=... TRANSPORT=http PORT=3456 node dist/index.js
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Health check: `GET http://localhost:3456/health`
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
npm install # Install dependencies
|
|
162
|
+
npm run build # Compile TypeScript → dist/
|
|
163
|
+
npm run typecheck # Type check without output
|
|
164
|
+
npm run dev # Watch mode for development
|
|
165
|
+
npm test # Run tests (vitest)
|
|
166
|
+
npm run test:watch # Run tests in watch mode
|
|
167
|
+
npm run test:coverage # Coverage report
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
MIT – see [LICENSE](LICENSE)
|
|
173
|
+
|
|
174
|
+
## Contributing
|
|
175
|
+
|
|
176
|
+
Contributions welcome! Please read [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
177
|
+
Repository: [codeberg.org/dpesch/mantisbt-mcp-server](https://codeberg.org/dpesch/mantisbt-mcp-server)
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { readFile, writeFile, unlink, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// MetadataCache
|
|
5
|
+
// ---------------------------------------------------------------------------
|
|
6
|
+
export class MetadataCache {
|
|
7
|
+
filePath;
|
|
8
|
+
ttlSeconds;
|
|
9
|
+
cacheDir;
|
|
10
|
+
constructor(cacheDir, ttlSeconds) {
|
|
11
|
+
this.cacheDir = cacheDir;
|
|
12
|
+
this.filePath = join(cacheDir, 'metadata.json');
|
|
13
|
+
this.ttlSeconds = ttlSeconds;
|
|
14
|
+
}
|
|
15
|
+
async isValid() {
|
|
16
|
+
try {
|
|
17
|
+
const raw = await readFile(this.filePath, 'utf-8');
|
|
18
|
+
const file = JSON.parse(raw);
|
|
19
|
+
const ageSeconds = (Date.now() - file.timestamp) / 1000;
|
|
20
|
+
return ageSeconds < this.ttlSeconds;
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async load() {
|
|
27
|
+
try {
|
|
28
|
+
const raw = await readFile(this.filePath, 'utf-8');
|
|
29
|
+
const file = JSON.parse(raw);
|
|
30
|
+
return file.data;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async save(data) {
|
|
37
|
+
await mkdir(this.cacheDir, { recursive: true });
|
|
38
|
+
const file = {
|
|
39
|
+
timestamp: Date.now(),
|
|
40
|
+
data,
|
|
41
|
+
};
|
|
42
|
+
await writeFile(this.filePath, JSON.stringify(file, null, 2), 'utf-8');
|
|
43
|
+
}
|
|
44
|
+
async invalidate() {
|
|
45
|
+
try {
|
|
46
|
+
await unlink(this.filePath);
|
|
47
|
+
}
|
|
48
|
+
catch {
|
|
49
|
+
// Already gone — that is fine
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// MantisApiError
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
export class MantisApiError extends Error {
|
|
5
|
+
statusCode;
|
|
6
|
+
constructor(statusCode, message) {
|
|
7
|
+
super(`MantisBT API error ${statusCode}: ${message}`);
|
|
8
|
+
this.statusCode = statusCode;
|
|
9
|
+
this.name = 'MantisApiError';
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export class MantisClient {
|
|
13
|
+
baseUrl;
|
|
14
|
+
apiKey;
|
|
15
|
+
responseObserver;
|
|
16
|
+
constructor(baseUrl, apiKey, responseObserver) {
|
|
17
|
+
// Ensure base URL ends without trailing slash; we always append /api/rest/
|
|
18
|
+
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
19
|
+
this.apiKey = apiKey;
|
|
20
|
+
this.responseObserver = responseObserver;
|
|
21
|
+
}
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// Private helpers
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
buildUrl(path, params) {
|
|
26
|
+
const url = new URL(`${this.baseUrl}/api/rest/${path}`);
|
|
27
|
+
if (params) {
|
|
28
|
+
for (const [key, value] of Object.entries(params)) {
|
|
29
|
+
if (value !== undefined) {
|
|
30
|
+
url.searchParams.set(key, String(value));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return url.toString();
|
|
35
|
+
}
|
|
36
|
+
headers() {
|
|
37
|
+
return {
|
|
38
|
+
'Authorization': this.apiKey,
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
'Accept': 'application/json',
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
async handleResponse(response) {
|
|
44
|
+
if (response.ok) {
|
|
45
|
+
this.responseObserver?.(response);
|
|
46
|
+
// Some DELETE endpoints return 204 No Content
|
|
47
|
+
if (response.status === 204) {
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
50
|
+
const text = await response.text();
|
|
51
|
+
if (!text)
|
|
52
|
+
return undefined;
|
|
53
|
+
return JSON.parse(text);
|
|
54
|
+
}
|
|
55
|
+
let message = response.statusText;
|
|
56
|
+
try {
|
|
57
|
+
const body = await response.text();
|
|
58
|
+
if (body) {
|
|
59
|
+
const parsed = JSON.parse(body);
|
|
60
|
+
if (parsed.message)
|
|
61
|
+
message = parsed.message;
|
|
62
|
+
else
|
|
63
|
+
message = body;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
// ignore parse errors — keep statusText as message
|
|
68
|
+
}
|
|
69
|
+
throw new MantisApiError(response.status, message);
|
|
70
|
+
}
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
// Public API methods
|
|
73
|
+
// ---------------------------------------------------------------------------
|
|
74
|
+
async get(path, params) {
|
|
75
|
+
const response = await fetch(this.buildUrl(path, params), {
|
|
76
|
+
method: 'GET',
|
|
77
|
+
headers: this.headers(),
|
|
78
|
+
});
|
|
79
|
+
return this.handleResponse(response);
|
|
80
|
+
}
|
|
81
|
+
async post(path, body) {
|
|
82
|
+
const response = await fetch(this.buildUrl(path), {
|
|
83
|
+
method: 'POST',
|
|
84
|
+
headers: this.headers(),
|
|
85
|
+
body: JSON.stringify(body),
|
|
86
|
+
});
|
|
87
|
+
return this.handleResponse(response);
|
|
88
|
+
}
|
|
89
|
+
async patch(path, body) {
|
|
90
|
+
const response = await fetch(this.buildUrl(path), {
|
|
91
|
+
method: 'PATCH',
|
|
92
|
+
headers: this.headers(),
|
|
93
|
+
body: JSON.stringify(body),
|
|
94
|
+
});
|
|
95
|
+
return this.handleResponse(response);
|
|
96
|
+
}
|
|
97
|
+
async delete(path) {
|
|
98
|
+
const response = await fetch(this.buildUrl(path), {
|
|
99
|
+
method: 'DELETE',
|
|
100
|
+
headers: this.headers(),
|
|
101
|
+
});
|
|
102
|
+
return this.handleResponse(response);
|
|
103
|
+
}
|
|
104
|
+
async getVersion() {
|
|
105
|
+
const response = await fetch(this.buildUrl('users/me'), {
|
|
106
|
+
method: 'GET',
|
|
107
|
+
headers: this.headers(),
|
|
108
|
+
});
|
|
109
|
+
if (!response.ok) {
|
|
110
|
+
throw new MantisApiError(response.status, response.statusText);
|
|
111
|
+
}
|
|
112
|
+
return response.headers.get('X-Mantis-Version') ?? 'unknown';
|
|
113
|
+
}
|
|
114
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Loader
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
async function readMantisJson() {
|
|
8
|
+
const filePath = join(homedir(), '.claude', 'mantis.json');
|
|
9
|
+
try {
|
|
10
|
+
const raw = await readFile(filePath, 'utf-8');
|
|
11
|
+
return JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
let cachedConfig = null;
|
|
18
|
+
export async function getConfig() {
|
|
19
|
+
if (cachedConfig)
|
|
20
|
+
return cachedConfig;
|
|
21
|
+
let baseUrl = process.env.MANTIS_BASE_URL ?? '';
|
|
22
|
+
let apiKey = process.env.MANTIS_API_KEY ?? '';
|
|
23
|
+
// If env vars are missing, try ~/.claude/mantis.json as fallback
|
|
24
|
+
if (!baseUrl || !apiKey) {
|
|
25
|
+
const json = await readMantisJson();
|
|
26
|
+
if (json) {
|
|
27
|
+
if (!baseUrl && json.base_url)
|
|
28
|
+
baseUrl = json.base_url;
|
|
29
|
+
if (!apiKey && json.api_key)
|
|
30
|
+
apiKey = json.api_key;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
const missing = [];
|
|
34
|
+
if (!baseUrl)
|
|
35
|
+
missing.push('MANTIS_BASE_URL');
|
|
36
|
+
if (!apiKey)
|
|
37
|
+
missing.push('MANTIS_API_KEY');
|
|
38
|
+
if (missing.length > 0) {
|
|
39
|
+
throw new Error(`Missing required MantisBT configuration: ${missing.join(', ')}.\n` +
|
|
40
|
+
`Set the environment variables or provide ~/.claude/mantis.json with keys "base_url" and "api_key".`);
|
|
41
|
+
}
|
|
42
|
+
const defaultCacheDir = join(homedir(), '.cache', 'mantisbt-mcp');
|
|
43
|
+
const cacheDir = process.env.MANTIS_CACHE_DIR ?? defaultCacheDir;
|
|
44
|
+
const cacheTtl = process.env.MANTIS_CACHE_TTL
|
|
45
|
+
? parseInt(process.env.MANTIS_CACHE_TTL, 10)
|
|
46
|
+
: 3600;
|
|
47
|
+
cachedConfig = {
|
|
48
|
+
baseUrl: baseUrl.replace(/\/$/, ''), // strip trailing slash
|
|
49
|
+
apiKey,
|
|
50
|
+
cacheDir,
|
|
51
|
+
cacheTtl,
|
|
52
|
+
};
|
|
53
|
+
return cachedConfig;
|
|
54
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// ---------------------------------------------------------------------------
|
|
2
|
+
// Relationship type IDs
|
|
3
|
+
// ---------------------------------------------------------------------------
|
|
4
|
+
// Note: the MantisBT REST API only accepts numeric type IDs, not string names.
|
|
5
|
+
export const RELATIONSHIP_TYPES = {
|
|
6
|
+
DUPLICATE_OF: 0,
|
|
7
|
+
RELATED_TO: 1,
|
|
8
|
+
PARENT_OF: 2, // "depends on" — this issue depends on the target
|
|
9
|
+
CHILD_OF: 3, // "blocks" — this issue blocks the target
|
|
10
|
+
HAS_DUPLICATE: 4,
|
|
11
|
+
};
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Status names (internal English names used in API calls)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export const STATUS_NAMES = [
|
|
16
|
+
'new',
|
|
17
|
+
'feedback',
|
|
18
|
+
'acknowledged',
|
|
19
|
+
'confirmed',
|
|
20
|
+
'assigned',
|
|
21
|
+
'resolved',
|
|
22
|
+
'closed',
|
|
23
|
+
];
|