@dpesch/mantisbt-mcp-server 1.5.9 → 1.6.1
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 +31 -0
- package/README.de.md +22 -11
- package/README.md +22 -11
- package/dist/client.js +37 -23
- package/dist/config.js +41 -50
- package/dist/index.js +23 -13
- package/dist/prompts/index.js +112 -0
- package/dist/resources/index.js +29 -0
- package/dist/tools/config.js +44 -40
- package/docs/cookbook.de.md +218 -0
- package/docs/cookbook.md +218 -0
- package/docs/examples.de.md +42 -0
- package/docs/examples.md +42 -0
- package/package.json +4 -3
- package/server.json +2 -2
- package/tests/client.test.ts +70 -0
- package/tests/config.test.ts +47 -37
- package/tests/helpers/mock-server.ts +61 -0
- package/tests/prompts/prompts.test.ts +242 -0
- package/tests/resources/resources.test.ts +192 -0
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,37 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.6.1] – 2026-03-24
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
- `repository.url` in package.json switched from Codeberg to GitHub mirror
|
|
14
|
+
for ecosystem compatibility (LobeHub, npm crawlers). Codeberg remains the
|
|
15
|
+
canonical source (see `homepage`).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## [1.6.0] – 2026-03-22
|
|
20
|
+
|
|
21
|
+
### Added
|
|
22
|
+
- Three MCP Resources for direct URI-addressable data access (read-only; less widely supported by clients than tools):
|
|
23
|
+
- `mantis://me` — profile of the authenticated API user (live fetch of `GET /users/me`)
|
|
24
|
+
- `mantis://projects` — all accessible MantisBT projects (cache-backed via MetadataCache; refresh with `sync_metadata`)
|
|
25
|
+
- `mantis://enums` — valid values for all issue enum fields (severity, priority, status, resolution, reproducibility); live fetch
|
|
26
|
+
- Four MCP prompt templates for guided issue workflows:
|
|
27
|
+
- `create-bug-report` — structured bug report prompt; collects project, category, summary, description, steps to reproduce, expected/actual behavior, and environment, then calls `create_issue`
|
|
28
|
+
- `create-feature-request` — feature request prompt; collects project, category, summary, description, and use case, then calls `create_issue`
|
|
29
|
+
- `summarize-issue` — calls `get_issue` for a given issue ID and returns a concise summary
|
|
30
|
+
- `project-status` — calls `list_issues` for a given project and produces a status report grouped by severity
|
|
31
|
+
- LobeHub marketplace badge added to README.md and README.de.md
|
|
32
|
+
|
|
33
|
+
### Removed
|
|
34
|
+
- Config file fallback (`~/.claude/mantis.json`): credentials must now be provided via environment variables (`MANTIS_BASE_URL`, `MANTIS_API_KEY`). The fallback was an internal migration aid with no value for external users.
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- Credential loading is now deferred to the first tool invocation. The server starts and responds to `tools/list` even when `MANTIS_BASE_URL` and `MANTIS_API_KEY` are not configured; the configuration error is surfaced when a tool is actually called. This enables marketplace validators (e.g. LobeHub) to probe the server without requiring credentials.
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
10
41
|
## [1.5.9] – 2026-03-20
|
|
11
42
|
|
|
12
43
|
### Fixed
|
package/README.de.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@dpesch/mantisbt-mcp-server)
|
|
4
4
|
[](LICENSE)
|
|
5
|
+
[](https://lobehub.com/mcp/dpesch-mantisbt-mcp-server)
|
|
5
6
|
[](https://glama.ai/mcp/servers/dpesch/mantisbt-mcp-server)
|
|
6
7
|
|
|
7
8
|
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.
|
|
@@ -79,17 +80,6 @@ npm run build
|
|
|
79
80
|
| `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. |
|
|
80
81
|
| `MANTIS_UPLOAD_DIR` | – | – | Schränkt `upload_file` auf Dateien in diesem Verzeichnis ein. Wenn gesetzt, wird jeder `file_path` außerhalb des Verzeichnisses abgelehnt (Pfad-Traversal-Versuche via `../` werden blockiert). Ohne diese Variable gilt keine Einschränkung. |
|
|
81
82
|
|
|
82
|
-
### Config-Datei (Fallback)
|
|
83
|
-
|
|
84
|
-
Falls keine Umgebungsvariablen gesetzt sind, wird `~/.claude/mantis.json` ausgelesen:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"base_url": "https://deine-mantis-instanz.example.com/api/rest",
|
|
89
|
-
"api_key": "dein-api-token"
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
83
|
## Verfügbare Tools
|
|
94
84
|
|
|
95
85
|
### Issues
|
|
@@ -197,6 +187,27 @@ npm install sqlite-vec better-sqlite3
|
|
|
197
187
|
| `get_mantis_version` | MantisBT-Version abrufen und auf Updates prüfen |
|
|
198
188
|
| `get_mcp_version` | Version dieser mantisbt-mcp-server-Instanz zurückgeben |
|
|
199
189
|
|
|
190
|
+
## Verfügbare Ressourcen
|
|
191
|
+
|
|
192
|
+
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.
|
|
193
|
+
|
|
194
|
+
| Ressource-URI | Beschreibung |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `mantis://me` | Profil des authentifizierten API-Benutzers (Live-Abruf) |
|
|
197
|
+
| `mantis://projects` | Alle zugänglichen MantisBT-Projekte (Cache-basiert, Aktualisierung via `sync_metadata`) |
|
|
198
|
+
| `mantis://enums` | Gültige Werte für alle Issue-Enum-Felder: Severity, Priority, Status, Resolution, Reproducibility (Live-Abruf) |
|
|
199
|
+
|
|
200
|
+
## Verfügbare Prompts
|
|
201
|
+
|
|
202
|
+
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.
|
|
203
|
+
|
|
204
|
+
| Prompt | Pflichtargumente | Optionale Argumente | Beschreibung |
|
|
205
|
+
|---|---|---|---|
|
|
206
|
+
| `create-bug-report` | `project_id`, `category`, `summary`, `description` | `steps_to_reproduce`, `expected`, `actual`, `environment` | Führt durch einen strukturierten Bug-Report und ruft `create_issue` auf |
|
|
207
|
+
| `create-feature-request` | `project_id`, `category`, `summary`, `description` | `use_case` | Führt durch einen Feature-Request und ruft `create_issue` auf |
|
|
208
|
+
| `summarize-issue` | `issue_id` | – | Ruft das Issue per `get_issue` ab und liefert eine prägnante Zusammenfassung |
|
|
209
|
+
| `project-status` | `project_id` | – | Listet Issues per `list_issues` auf und erstellt einen Status-Report nach Schweregrad |
|
|
210
|
+
|
|
200
211
|
## HTTP-Modus
|
|
201
212
|
|
|
202
213
|
Für den Einsatz als eigenständiger Server (z.B. in Remote-Setups):
|
package/README.md
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/@dpesch/mantisbt-mcp-server)
|
|
4
4
|
[](LICENSE)
|
|
5
|
+
[](https://lobehub.com/mcp/dpesch-mantisbt-mcp-server)
|
|
5
6
|
[](https://glama.ai/mcp/servers/dpesch/mantisbt-mcp-server)
|
|
6
7
|
|
|
7
8
|
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.
|
|
@@ -79,17 +80,6 @@ npm run build
|
|
|
79
80
|
| `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. |
|
|
80
81
|
| `MANTIS_UPLOAD_DIR` | – | – | Restrict `upload_file` to files within this directory. When set, any `file_path` outside the directory is rejected (path traversal attempts via `../` are blocked). Without this variable there is no restriction. |
|
|
81
82
|
|
|
82
|
-
### Config file (fallback)
|
|
83
|
-
|
|
84
|
-
If no environment variables are set, `~/.claude/mantis.json` is read:
|
|
85
|
-
|
|
86
|
-
```json
|
|
87
|
-
{
|
|
88
|
-
"base_url": "https://your-mantis.example.com/api/rest",
|
|
89
|
-
"api_key": "your-api-token"
|
|
90
|
-
}
|
|
91
|
-
```
|
|
92
|
-
|
|
93
83
|
## Available tools
|
|
94
84
|
|
|
95
85
|
### Issues
|
|
@@ -197,6 +187,27 @@ npm install sqlite-vec better-sqlite3
|
|
|
197
187
|
| `get_mantis_version` | Get MantisBT version and check for updates |
|
|
198
188
|
| `get_mcp_version` | Return the version of this mantisbt-mcp-server instance |
|
|
199
189
|
|
|
190
|
+
## Available resources
|
|
191
|
+
|
|
192
|
+
MCP Resources are URI-addressable, read-only data that clients can fetch directly without calling a tool. They are the third MCP primitive alongside Tools and Prompts. Note that Resource support is less widely implemented in MCP clients than Tools — check your client's documentation.
|
|
193
|
+
|
|
194
|
+
| Resource URI | Description |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `mantis://me` | Profile of the authenticated API user (live fetch) |
|
|
197
|
+
| `mantis://projects` | All accessible MantisBT projects (cache-backed, refreshed via `sync_metadata`) |
|
|
198
|
+
| `mantis://enums` | Valid values for all issue enum fields: severity, priority, status, resolution, reproducibility (live fetch) |
|
|
199
|
+
|
|
200
|
+
## Available prompts
|
|
201
|
+
|
|
202
|
+
MCP prompt templates are conversation starters that instruct the LLM to collect structured input and then call the appropriate tool. They are not tools themselves — they initiate a guided workflow.
|
|
203
|
+
|
|
204
|
+
| Prompt | Required args | Optional args | Description |
|
|
205
|
+
|---|---|---|---|
|
|
206
|
+
| `create-bug-report` | `project_id`, `category`, `summary`, `description` | `steps_to_reproduce`, `expected`, `actual`, `environment` | Guides through a structured bug report and calls `create_issue` |
|
|
207
|
+
| `create-feature-request` | `project_id`, `category`, `summary`, `description` | `use_case` | Guides through a feature request and calls `create_issue` |
|
|
208
|
+
| `summarize-issue` | `issue_id` | – | Fetches an issue via `get_issue` and returns a concise summary |
|
|
209
|
+
| `project-status` | `project_id` | – | Lists issues via `list_issues` and generates a status report grouped by severity |
|
|
210
|
+
|
|
200
211
|
## HTTP mode
|
|
201
212
|
|
|
202
213
|
For use as a standalone server (e.g. in remote setups):
|
package/dist/client.js
CHANGED
|
@@ -10,20 +10,32 @@ export class MantisApiError extends Error {
|
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
export class MantisClient {
|
|
13
|
-
|
|
14
|
-
apiKey;
|
|
13
|
+
credentialFactory;
|
|
15
14
|
responseObserver;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
resolvedCredentials;
|
|
16
|
+
constructor(baseUrlOrFactory, apiKeyOrObserver, responseObserver) {
|
|
17
|
+
if (typeof baseUrlOrFactory === 'string') {
|
|
18
|
+
this.resolvedCredentials = { baseUrl: baseUrlOrFactory.replace(/\/$/, ''), apiKey: apiKeyOrObserver };
|
|
19
|
+
this.responseObserver = responseObserver;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
this.credentialFactory = baseUrlOrFactory;
|
|
23
|
+
this.responseObserver = apiKeyOrObserver;
|
|
24
|
+
}
|
|
21
25
|
}
|
|
22
26
|
// ---------------------------------------------------------------------------
|
|
23
27
|
// Private helpers
|
|
24
28
|
// ---------------------------------------------------------------------------
|
|
25
|
-
|
|
26
|
-
|
|
29
|
+
async getCredentials() {
|
|
30
|
+
if (!this.resolvedCredentials) {
|
|
31
|
+
const { baseUrl, apiKey } = await this.credentialFactory();
|
|
32
|
+
this.resolvedCredentials = { baseUrl: baseUrl.replace(/\/$/, ''), apiKey };
|
|
33
|
+
}
|
|
34
|
+
return this.resolvedCredentials;
|
|
35
|
+
}
|
|
36
|
+
async buildUrl(path, params) {
|
|
37
|
+
const { baseUrl } = await this.getCredentials();
|
|
38
|
+
const url = new URL(`${baseUrl}/api/rest/${path}`);
|
|
27
39
|
if (params) {
|
|
28
40
|
for (const [key, value] of Object.entries(params)) {
|
|
29
41
|
if (value !== undefined) {
|
|
@@ -33,9 +45,10 @@ export class MantisClient {
|
|
|
33
45
|
}
|
|
34
46
|
return url.toString();
|
|
35
47
|
}
|
|
36
|
-
headers() {
|
|
48
|
+
async headers() {
|
|
49
|
+
const { apiKey } = await this.getCredentials();
|
|
37
50
|
return {
|
|
38
|
-
'Authorization':
|
|
51
|
+
'Authorization': apiKey,
|
|
39
52
|
'Content-Type': 'application/json',
|
|
40
53
|
'Accept': 'application/json',
|
|
41
54
|
};
|
|
@@ -72,42 +85,43 @@ export class MantisClient {
|
|
|
72
85
|
// Public API methods
|
|
73
86
|
// ---------------------------------------------------------------------------
|
|
74
87
|
async get(path, params) {
|
|
75
|
-
const response = await fetch(this.buildUrl(path, params), {
|
|
88
|
+
const response = await fetch(await this.buildUrl(path, params), {
|
|
76
89
|
method: 'GET',
|
|
77
|
-
headers: this.headers(),
|
|
90
|
+
headers: await this.headers(),
|
|
78
91
|
});
|
|
79
92
|
return this.handleResponse(response);
|
|
80
93
|
}
|
|
81
94
|
async post(path, body) {
|
|
82
|
-
const response = await fetch(this.buildUrl(path), {
|
|
95
|
+
const response = await fetch(await this.buildUrl(path), {
|
|
83
96
|
method: 'POST',
|
|
84
|
-
headers: this.headers(),
|
|
97
|
+
headers: await this.headers(),
|
|
85
98
|
body: JSON.stringify(body),
|
|
86
99
|
});
|
|
87
100
|
return this.handleResponse(response);
|
|
88
101
|
}
|
|
89
102
|
async patch(path, body) {
|
|
90
|
-
const response = await fetch(this.buildUrl(path), {
|
|
103
|
+
const response = await fetch(await this.buildUrl(path), {
|
|
91
104
|
method: 'PATCH',
|
|
92
|
-
headers: this.headers(),
|
|
105
|
+
headers: await this.headers(),
|
|
93
106
|
body: JSON.stringify(body),
|
|
94
107
|
});
|
|
95
108
|
return this.handleResponse(response);
|
|
96
109
|
}
|
|
97
110
|
async delete(path) {
|
|
98
|
-
const response = await fetch(this.buildUrl(path), {
|
|
111
|
+
const response = await fetch(await this.buildUrl(path), {
|
|
99
112
|
method: 'DELETE',
|
|
100
|
-
headers: this.headers(),
|
|
113
|
+
headers: await this.headers(),
|
|
101
114
|
});
|
|
102
115
|
return this.handleResponse(response);
|
|
103
116
|
}
|
|
104
117
|
async postFormData(path, formData) {
|
|
105
118
|
// Note: Content-Type must NOT be set here — fetch sets it automatically
|
|
106
119
|
// with the correct multipart/form-data boundary.
|
|
107
|
-
const
|
|
120
|
+
const { apiKey } = await this.getCredentials();
|
|
121
|
+
const response = await fetch(await this.buildUrl(path), {
|
|
108
122
|
method: 'POST',
|
|
109
123
|
headers: {
|
|
110
|
-
'Authorization':
|
|
124
|
+
'Authorization': apiKey,
|
|
111
125
|
'Accept': 'application/json',
|
|
112
126
|
},
|
|
113
127
|
body: formData,
|
|
@@ -115,9 +129,9 @@ export class MantisClient {
|
|
|
115
129
|
return this.handleResponse(response);
|
|
116
130
|
}
|
|
117
131
|
async getVersion() {
|
|
118
|
-
const response = await fetch(this.buildUrl('users/me'), {
|
|
132
|
+
const response = await fetch(await this.buildUrl('users/me'), {
|
|
119
133
|
method: 'GET',
|
|
120
|
-
headers: this.headers(),
|
|
134
|
+
headers: await this.headers(),
|
|
121
135
|
});
|
|
122
136
|
if (!response.ok) {
|
|
123
137
|
throw new MantisApiError(response.status, response.statusText);
|
package/dist/config.js
CHANGED
|
@@ -23,45 +23,7 @@ async function loadDotEnvLocal() {
|
|
|
23
23
|
// .env.local not present — use environment variables directly
|
|
24
24
|
}
|
|
25
25
|
}
|
|
26
|
-
|
|
27
|
-
// Loader
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
async function readMantisJson() {
|
|
30
|
-
const filePath = join(homedir(), '.claude', 'mantis.json');
|
|
31
|
-
try {
|
|
32
|
-
const raw = await readFile(filePath, 'utf-8');
|
|
33
|
-
return JSON.parse(raw);
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return null;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
let cachedConfig = null;
|
|
40
|
-
export async function getConfig() {
|
|
41
|
-
if (cachedConfig)
|
|
42
|
-
return cachedConfig;
|
|
43
|
-
await loadDotEnvLocal();
|
|
44
|
-
let baseUrl = process.env.MANTIS_BASE_URL ?? '';
|
|
45
|
-
let apiKey = process.env.MANTIS_API_KEY ?? '';
|
|
46
|
-
// If env vars are missing, try ~/.claude/mantis.json as fallback
|
|
47
|
-
if (!baseUrl || !apiKey) {
|
|
48
|
-
const json = await readMantisJson();
|
|
49
|
-
if (json) {
|
|
50
|
-
if (!baseUrl && json.base_url)
|
|
51
|
-
baseUrl = json.base_url;
|
|
52
|
-
if (!apiKey && json.api_key)
|
|
53
|
-
apiKey = json.api_key;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
const missing = [];
|
|
57
|
-
if (!baseUrl)
|
|
58
|
-
missing.push('MANTIS_BASE_URL');
|
|
59
|
-
if (!apiKey)
|
|
60
|
-
missing.push('MANTIS_API_KEY');
|
|
61
|
-
if (missing.length > 0) {
|
|
62
|
-
throw new Error(`Missing required MantisBT configuration: ${missing.join(', ')}.\n` +
|
|
63
|
-
`Set the environment variables or provide ~/.claude/mantis.json with keys "base_url" and "api_key".`);
|
|
64
|
-
}
|
|
26
|
+
function readNonCredentialConfig() {
|
|
65
27
|
const defaultCacheDir = join(homedir(), '.cache', 'mantisbt-mcp');
|
|
66
28
|
const cacheDir = process.env.MANTIS_CACHE_DIR ?? defaultCacheDir;
|
|
67
29
|
const cacheTtl = process.env.MANTIS_CACHE_TTL
|
|
@@ -77,19 +39,13 @@ export async function getConfig() {
|
|
|
77
39
|
const searchModelName = process.env.MANTIS_SEARCH_MODEL ??
|
|
78
40
|
'Xenova/paraphrase-multilingual-MiniLM-L12-v2';
|
|
79
41
|
const searchNumThreads = Math.max(1, parseInt(process.env.MANTIS_SEARCH_THREADS ?? '', 10) || 1);
|
|
80
|
-
|
|
81
|
-
const httpHost = process.env.MCP_HTTP_HOST ?? '127.0.0.1';
|
|
82
|
-
const httpPort = parseInt(process.env.PORT ?? '3000', 10);
|
|
83
|
-
const httpToken = process.env.MCP_HTTP_TOKEN;
|
|
84
|
-
cachedConfig = {
|
|
85
|
-
baseUrl: baseUrl.replace(/\/$/, ''), // strip trailing slash
|
|
86
|
-
apiKey,
|
|
42
|
+
return {
|
|
87
43
|
cacheDir,
|
|
88
44
|
cacheTtl,
|
|
89
|
-
uploadDir,
|
|
90
|
-
httpHost,
|
|
91
|
-
httpPort,
|
|
92
|
-
httpToken,
|
|
45
|
+
uploadDir: process.env.MANTIS_UPLOAD_DIR,
|
|
46
|
+
httpHost: process.env.MCP_HTTP_HOST ?? '127.0.0.1',
|
|
47
|
+
httpPort: parseInt(process.env.PORT ?? '3000', 10),
|
|
48
|
+
httpToken: process.env.MCP_HTTP_TOKEN,
|
|
93
49
|
search: {
|
|
94
50
|
enabled: searchEnabled,
|
|
95
51
|
backend: searchBackend,
|
|
@@ -98,5 +54,40 @@ export async function getConfig() {
|
|
|
98
54
|
numThreads: searchNumThreads,
|
|
99
55
|
},
|
|
100
56
|
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Returns all non-credential config values. Never throws, even when
|
|
60
|
+
* MANTIS_BASE_URL / MANTIS_API_KEY are absent. Use this at server startup
|
|
61
|
+
* so the MCP transport can connect and respond to tools/list without
|
|
62
|
+
* requiring credentials to be configured.
|
|
63
|
+
*/
|
|
64
|
+
export async function getStartupConfig() {
|
|
65
|
+
await loadDotEnvLocal();
|
|
66
|
+
return readNonCredentialConfig();
|
|
67
|
+
}
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Full config (credentials required)
|
|
70
|
+
// ---------------------------------------------------------------------------
|
|
71
|
+
let cachedConfig = null;
|
|
72
|
+
export async function getConfig() {
|
|
73
|
+
if (cachedConfig)
|
|
74
|
+
return cachedConfig;
|
|
75
|
+
await loadDotEnvLocal();
|
|
76
|
+
const baseUrl = process.env.MANTIS_BASE_URL ?? '';
|
|
77
|
+
const apiKey = process.env.MANTIS_API_KEY ?? '';
|
|
78
|
+
const missing = [];
|
|
79
|
+
if (!baseUrl)
|
|
80
|
+
missing.push('MANTIS_BASE_URL');
|
|
81
|
+
if (!apiKey)
|
|
82
|
+
missing.push('MANTIS_API_KEY');
|
|
83
|
+
if (missing.length > 0) {
|
|
84
|
+
throw new Error(`Missing required MantisBT configuration: ${missing.join(', ')}.\n` +
|
|
85
|
+
`Set the environment variables MANTIS_BASE_URL and MANTIS_API_KEY.`);
|
|
86
|
+
}
|
|
87
|
+
cachedConfig = {
|
|
88
|
+
baseUrl: baseUrl.replace(/\/$/, ''), // strip trailing slash
|
|
89
|
+
apiKey,
|
|
90
|
+
...readNonCredentialConfig(),
|
|
91
|
+
};
|
|
101
92
|
return cachedConfig;
|
|
102
93
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,7 +6,7 @@ import { createServer } from 'node:http';
|
|
|
6
6
|
import { readFileSync } from 'node:fs';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
8
8
|
import { join, dirname } from 'node:path';
|
|
9
|
-
import { getConfig } from './config.js';
|
|
9
|
+
import { getConfig, getStartupConfig } from './config.js';
|
|
10
10
|
import { MantisClient } from './client.js';
|
|
11
11
|
import { VersionHintService, setGlobalVersionHint } from './version-hint.js';
|
|
12
12
|
import { MetadataCache } from './cache.js';
|
|
@@ -22,6 +22,8 @@ import { registerConfigTools } from './tools/config.js';
|
|
|
22
22
|
import { registerMetadataTools } from './tools/metadata.js';
|
|
23
23
|
import { registerTagTools } from './tools/tags.js';
|
|
24
24
|
import { registerVersionTools } from './tools/version.js';
|
|
25
|
+
import { registerPrompts } from './prompts/index.js';
|
|
26
|
+
import { registerResources } from './resources/index.js';
|
|
25
27
|
// ---------------------------------------------------------------------------
|
|
26
28
|
// Read version from package.json
|
|
27
29
|
// ---------------------------------------------------------------------------
|
|
@@ -33,18 +35,24 @@ const version = packageJson.version;
|
|
|
33
35
|
// Bootstrap
|
|
34
36
|
// ---------------------------------------------------------------------------
|
|
35
37
|
async function createMcpServer() {
|
|
36
|
-
|
|
38
|
+
// Use startup config (no credentials required) so the server can connect
|
|
39
|
+
// and respond to tools/list even without MANTIS_BASE_URL / MANTIS_API_KEY.
|
|
40
|
+
// Credentials are resolved lazily on the first actual tool invocation.
|
|
41
|
+
const startupConfig = await getStartupConfig();
|
|
37
42
|
const versionHint = new VersionHintService();
|
|
38
43
|
setGlobalVersionHint(versionHint);
|
|
39
|
-
const client = new MantisClient(
|
|
40
|
-
|
|
44
|
+
const client = new MantisClient(async () => {
|
|
45
|
+
const config = await getConfig();
|
|
46
|
+
return { baseUrl: config.baseUrl, apiKey: config.apiKey };
|
|
47
|
+
}, (response) => versionHint.onSuccessfulResponse(response));
|
|
48
|
+
const cache = new MetadataCache(startupConfig.cacheDir, startupConfig.cacheTtl);
|
|
41
49
|
const server = new McpServer({
|
|
42
50
|
name: 'mantisbt-mcp-server',
|
|
43
51
|
version,
|
|
44
52
|
});
|
|
45
53
|
registerIssueTools(server, client, cache);
|
|
46
54
|
registerNoteTools(server, client);
|
|
47
|
-
registerFileTools(server, client,
|
|
55
|
+
registerFileTools(server, client, startupConfig.uploadDir);
|
|
48
56
|
registerRelationshipTools(server, client);
|
|
49
57
|
registerMonitorTools(server, client);
|
|
50
58
|
registerProjectTools(server, client);
|
|
@@ -54,10 +62,12 @@ async function createMcpServer() {
|
|
|
54
62
|
registerMetadataTools(server, client, cache);
|
|
55
63
|
registerTagTools(server, client);
|
|
56
64
|
registerVersionTools(server, client, versionHint, version);
|
|
65
|
+
registerPrompts(server);
|
|
66
|
+
registerResources(server, client, cache);
|
|
57
67
|
// Optional: Semantic search module
|
|
58
|
-
if (
|
|
68
|
+
if (startupConfig.search.enabled) {
|
|
59
69
|
const { initializeSearchModule } = await import('./search/index.js');
|
|
60
|
-
await initializeSearchModule(server, client,
|
|
70
|
+
await initializeSearchModule(server, client, startupConfig.search);
|
|
61
71
|
}
|
|
62
72
|
return server;
|
|
63
73
|
}
|
|
@@ -75,14 +85,14 @@ async function runStdio() {
|
|
|
75
85
|
process.stdin.once('close', () => process.exit(0));
|
|
76
86
|
}
|
|
77
87
|
async function runHttp() {
|
|
78
|
-
const
|
|
88
|
+
const startupConfig = await getStartupConfig();
|
|
79
89
|
const server = await createMcpServer();
|
|
80
|
-
const port =
|
|
90
|
+
const port = startupConfig.httpPort;
|
|
81
91
|
const httpServer = createServer(async (req, res) => {
|
|
82
92
|
if (req.method === 'POST' && req.url === '/mcp') {
|
|
83
|
-
if (
|
|
93
|
+
if (startupConfig.httpToken) {
|
|
84
94
|
const auth = req.headers['authorization'];
|
|
85
|
-
if (auth !== `Bearer ${
|
|
95
|
+
if (auth !== `Bearer ${startupConfig.httpToken}`) {
|
|
86
96
|
res.writeHead(401, { 'Content-Type': 'application/json' });
|
|
87
97
|
res.end(JSON.stringify({ error: 'Unauthorized' }));
|
|
88
98
|
return;
|
|
@@ -116,8 +126,8 @@ async function runHttp() {
|
|
|
116
126
|
res.end();
|
|
117
127
|
}
|
|
118
128
|
});
|
|
119
|
-
httpServer.listen(port,
|
|
120
|
-
console.error(`MantisBT MCP Server v${version} running on http://${
|
|
129
|
+
httpServer.listen(port, startupConfig.httpHost, () => {
|
|
130
|
+
console.error(`MantisBT MCP Server v${version} running on http://${startupConfig.httpHost}:${port}/mcp`);
|
|
121
131
|
});
|
|
122
132
|
}
|
|
123
133
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
function userMessage(text) {
|
|
3
|
+
return {
|
|
4
|
+
messages: [{
|
|
5
|
+
role: 'user',
|
|
6
|
+
content: { type: 'text', text },
|
|
7
|
+
}],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
export function registerPrompts(server) {
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
// create-bug-report
|
|
13
|
+
// ---------------------------------------------------------------------------
|
|
14
|
+
server.registerPrompt('create-bug-report', {
|
|
15
|
+
title: 'Create Bug Report',
|
|
16
|
+
description: 'Guide the creation of a complete bug report in MantisBT. Project, category, summary, and description are required.',
|
|
17
|
+
argsSchema: {
|
|
18
|
+
project_id: z.coerce.number().int().positive().describe('Numeric project ID'),
|
|
19
|
+
category: z.string().describe('Issue category (call get_project_categories to list valid values)'),
|
|
20
|
+
summary: z.string().describe('Short, descriptive summary of the bug'),
|
|
21
|
+
description: z.string().describe('Detailed description of the bug'),
|
|
22
|
+
steps_to_reproduce: z.string().optional().describe('Step-by-step reproduction instructions'),
|
|
23
|
+
expected: z.string().optional().describe('Expected behavior'),
|
|
24
|
+
actual: z.string().optional().describe('Actual (buggy) behavior'),
|
|
25
|
+
environment: z.string().optional().describe('Environment info (OS, browser, version, etc.)'),
|
|
26
|
+
},
|
|
27
|
+
}, ({ project_id, category, summary, description, steps_to_reproduce, expected, actual, environment }) => {
|
|
28
|
+
const lines = [
|
|
29
|
+
`Create a bug report in MantisBT for project ${project_id}.`,
|
|
30
|
+
``,
|
|
31
|
+
`Category: ${category}`,
|
|
32
|
+
`Summary: ${summary}`,
|
|
33
|
+
``,
|
|
34
|
+
`Description:`,
|
|
35
|
+
description,
|
|
36
|
+
];
|
|
37
|
+
if (steps_to_reproduce) {
|
|
38
|
+
lines.push(``, `Steps to reproduce:`, steps_to_reproduce);
|
|
39
|
+
}
|
|
40
|
+
if (expected) {
|
|
41
|
+
lines.push(``, `Expected behavior:`, expected);
|
|
42
|
+
}
|
|
43
|
+
if (actual) {
|
|
44
|
+
lines.push(``, `Actual behavior:`, actual);
|
|
45
|
+
}
|
|
46
|
+
if (environment) {
|
|
47
|
+
lines.push(``, `Environment:`, environment);
|
|
48
|
+
}
|
|
49
|
+
lines.push(``, `Use the create_issue tool to submit this bug report.`, `Call get_issue_enums first if you need to look up valid severity and priority values.`, `Default to severity "minor" and priority "normal" unless the user specifies otherwise.`);
|
|
50
|
+
return userMessage(lines.join('\n'));
|
|
51
|
+
});
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// create-feature-request
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
server.registerPrompt('create-feature-request', {
|
|
56
|
+
title: 'Create Feature Request',
|
|
57
|
+
description: 'Guide the creation of a feature request in MantisBT. Project, category, summary, and description are required.',
|
|
58
|
+
argsSchema: {
|
|
59
|
+
project_id: z.coerce.number().int().positive().describe('Numeric project ID'),
|
|
60
|
+
category: z.string().describe('Issue category (call get_project_categories to list valid values)'),
|
|
61
|
+
summary: z.string().describe('Short description of the requested feature'),
|
|
62
|
+
description: z.string().describe('Detailed description of the feature'),
|
|
63
|
+
use_case: z.string().optional().describe('Business use case or motivation for the feature'),
|
|
64
|
+
},
|
|
65
|
+
}, ({ project_id, category, summary, description, use_case }) => {
|
|
66
|
+
const lines = [
|
|
67
|
+
`Create a feature request in MantisBT for project ${project_id}.`,
|
|
68
|
+
``,
|
|
69
|
+
`Category: ${category}`,
|
|
70
|
+
`Summary: ${summary}`,
|
|
71
|
+
``,
|
|
72
|
+
`Description:`,
|
|
73
|
+
description,
|
|
74
|
+
];
|
|
75
|
+
if (use_case) {
|
|
76
|
+
lines.push(``, `Use case / motivation:`, use_case);
|
|
77
|
+
}
|
|
78
|
+
lines.push(``, `Use the create_issue tool to submit this feature request.`, `Call get_issue_enums first to look up valid severity values — use severity "feature" and priority "normal" unless the user specifies otherwise.`);
|
|
79
|
+
return userMessage(lines.join('\n'));
|
|
80
|
+
});
|
|
81
|
+
// ---------------------------------------------------------------------------
|
|
82
|
+
// summarize-issue
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
server.registerPrompt('summarize-issue', {
|
|
85
|
+
title: 'Summarize Issue',
|
|
86
|
+
description: 'Fetch a MantisBT issue and provide a concise summary of its status, details, and recent activity.',
|
|
87
|
+
argsSchema: {
|
|
88
|
+
issue_id: z.coerce.number().int().positive().describe('Numeric issue ID'),
|
|
89
|
+
},
|
|
90
|
+
}, ({ issue_id }) => userMessage([
|
|
91
|
+
`Fetch issue #${issue_id} using the get_issue tool and provide a concise summary.`,
|
|
92
|
+
`Include: status, assignee, severity, priority, description, and any recent notes.`,
|
|
93
|
+
`Highlight blockers, unresolved questions, or anything that requires attention.`,
|
|
94
|
+
].join('\n')));
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
// project-status
|
|
97
|
+
// ---------------------------------------------------------------------------
|
|
98
|
+
server.registerPrompt('project-status', {
|
|
99
|
+
title: 'Project Status Report',
|
|
100
|
+
description: 'Generate a status overview of open issues for a MantisBT project, grouped by severity.',
|
|
101
|
+
argsSchema: {
|
|
102
|
+
project_id: z.coerce.number().int().positive().describe('Numeric project ID'),
|
|
103
|
+
},
|
|
104
|
+
}, ({ project_id }) => userMessage([
|
|
105
|
+
`Use the list_issues tool to fetch open issues for MantisBT project ${project_id}.`,
|
|
106
|
+
`Produce a status report that includes:`,
|
|
107
|
+
`- Total count of open issues`,
|
|
108
|
+
`- Breakdown by severity (critical and major issues first)`,
|
|
109
|
+
`- Breakdown by assignee`,
|
|
110
|
+
`- A short list of the most critical or longest-open issues that need attention`,
|
|
111
|
+
].join('\n')));
|
|
112
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { fetchIssueEnums } from '../tools/config.js';
|
|
2
|
+
function jsonResource(uri, data) {
|
|
3
|
+
return {
|
|
4
|
+
contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }],
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function registerResources(server, client, cache) {
|
|
8
|
+
server.registerResource('current-user', 'mantis://me', {
|
|
9
|
+
title: 'Current User',
|
|
10
|
+
description: 'Profile of the user associated with the configured API key.',
|
|
11
|
+
mimeType: 'application/json',
|
|
12
|
+
}, async (uri) => jsonResource(uri, await client.get('users/me')));
|
|
13
|
+
server.registerResource('projects', 'mantis://projects', {
|
|
14
|
+
title: 'Projects',
|
|
15
|
+
description: 'All MantisBT projects accessible to the current API user. Served from local cache when fresh; falls back to live fetch. Refresh via the sync_metadata tool.',
|
|
16
|
+
mimeType: 'application/json',
|
|
17
|
+
}, async (uri) => {
|
|
18
|
+
const cached = await cache.loadIfValid();
|
|
19
|
+
const projects = cached?.projects
|
|
20
|
+
?? (await client.get('projects')).projects
|
|
21
|
+
?? [];
|
|
22
|
+
return jsonResource(uri, projects);
|
|
23
|
+
});
|
|
24
|
+
server.registerResource('issue-enums', 'mantis://enums', {
|
|
25
|
+
title: 'Issue Enums',
|
|
26
|
+
description: 'Valid values for issue enum fields: severity, priority, status, resolution, and reproducibility. Use these to look up IDs or names before creating or updating issues.',
|
|
27
|
+
mimeType: 'application/json',
|
|
28
|
+
}, async (uri) => jsonResource(uri, await fetchIssueEnums(client)));
|
|
29
|
+
}
|