@dpesch/mantisbt-mcp-server 1.5.8 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -7,6 +7,40 @@ This project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## [1.6.0] – 2026-03-22
11
+
12
+ ### Added
13
+ - Three MCP Resources for direct URI-addressable data access (read-only; less widely supported by clients than tools):
14
+ - `mantis://me` — profile of the authenticated API user (live fetch of `GET /users/me`)
15
+ - `mantis://projects` — all accessible MantisBT projects (cache-backed via MetadataCache; refresh with `sync_metadata`)
16
+ - `mantis://enums` — valid values for all issue enum fields (severity, priority, status, resolution, reproducibility); live fetch
17
+ - Four MCP prompt templates for guided issue workflows:
18
+ - `create-bug-report` — structured bug report prompt; collects project, category, summary, description, steps to reproduce, expected/actual behavior, and environment, then calls `create_issue`
19
+ - `create-feature-request` — feature request prompt; collects project, category, summary, description, and use case, then calls `create_issue`
20
+ - `summarize-issue` — calls `get_issue` for a given issue ID and returns a concise summary
21
+ - `project-status` — calls `list_issues` for a given project and produces a status report grouped by severity
22
+ - LobeHub marketplace badge added to README.md and README.de.md
23
+
24
+ ### Removed
25
+ - 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.
26
+
27
+ ### Changed
28
+ - 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.
29
+
30
+ ---
31
+
32
+ ## [1.5.9] – 2026-03-20
33
+
34
+ ### Fixed
35
+ - `create_issue` now validates `severity` and `priority` against canonical English names and returns a clear error for unknown values; sends `{ id }` instead of `{ name }` to work correctly on localized MantisBT installations
36
+
37
+ ### Added
38
+ - Cookbook (EN + DE): tool-oriented recipes with copy-paste-ready parameter examples for all registered tools ([docs/cookbook.md](docs/cookbook.md), [docs/cookbook.de.md](docs/cookbook.de.md))
39
+ - Usage examples (EN + DE): natural language prompt examples for everyday use cases ([docs/examples.md](docs/examples.md), [docs/examples.de.md](docs/examples.de.md))
40
+ - README: documentation section linking to cookbook and examples
41
+
42
+ ---
43
+
10
44
  ## [1.5.8] – 2026-03-18
11
45
 
12
46
  ### Added
package/README.de.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@dpesch/mantisbt-mcp-server)](https://www.npmjs.com/package/@dpesch/mantisbt-mcp-server)
4
4
  [![license](https://img.shields.io/npm/l/@dpesch/mantisbt-mcp-server)](LICENSE)
5
+ [![MCP Badge](https://lobehub.com/badge/mcp/dpesch-mantisbt-mcp-server)](https://lobehub.com/mcp/dpesch-mantisbt-mcp-server)
5
6
  [![MantisBT MCP Server](https://glama.ai/mcp/servers/dpesch/mantisbt-mcp-server/badges/card.svg)](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
@@ -98,7 +88,7 @@ Falls keine Umgebungsvariablen gesetzt sind, wird `~/.claude/mantis.json` ausgel
98
88
  |---|---|
99
89
  | `get_issue` | Ein Issue anhand seiner ID abrufen |
100
90
  | `list_issues` | Issues nach Projekt, Status, Autor u.v.m. filtern; optionales `select` für Feldprojektion und `status` für clientseitige Statusfilterung |
101
- | `create_issue` | Neues Issue anlegen; optionaler `handler`-Parameter akzeptiert einen Benutzernamen als Alternative zu `handler_id` (wird gegen die Projektmitglieder aufgelöst) |
91
+ | `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) |
102
92
  | `update_issue` | Bestehendes Issue bearbeiten |
103
93
  | `delete_issue` | Issue löschen |
104
94
 
@@ -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):
@@ -211,6 +222,11 @@ MANTIS_BASE_URL=... MANTIS_API_KEY=... TRANSPORT=http PORT=3456 node dist/index.
211
222
 
212
223
  Healthcheck: `GET http://localhost:3456/health` (immer öffentlich, kein Token erforderlich)
213
224
 
225
+ ## Dokumentation
226
+
227
+ - [**Cookbook**](docs/cookbook.de.md) — Tool-orientierte Rezepte mit direkt verwendbaren Parameter-Beispielen für alle registrierten Tools
228
+ - [**Anwendungsbeispiele**](docs/examples.de.md) — Beispiele in natürlicher Sprache für alltägliche Anwendungsfälle (keine Tool-Namen erforderlich)
229
+
214
230
  ## Entwicklung
215
231
 
216
232
  ```bash
package/README.md CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/@dpesch/mantisbt-mcp-server)](https://www.npmjs.com/package/@dpesch/mantisbt-mcp-server)
4
4
  [![license](https://img.shields.io/npm/l/@dpesch/mantisbt-mcp-server)](LICENSE)
5
+ [![MCP Badge](https://lobehub.com/badge/mcp/dpesch-mantisbt-mcp-server)](https://lobehub.com/mcp/dpesch-mantisbt-mcp-server)
5
6
  [![MantisBT MCP Server](https://glama.ai/mcp/servers/dpesch/mantisbt-mcp-server/badges/card.svg)](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
@@ -98,7 +88,7 @@ If no environment variables are set, `~/.claude/mantis.json` is read:
98
88
  |---|---|
99
89
  | `get_issue` | Retrieve an issue by its numeric ID |
100
90
  | `list_issues` | Filter issues by project, status, author, and more; optional `select` for field projection and `status` for client-side status filtering |
101
- | `create_issue` | Create a new issue; optional `handler` parameter accepts a username as alternative to `handler_id` (resolved against project members) |
91
+ | `create_issue` | Create a new issue; `severity` and `priority` must be canonical English names (e.g. `minor`, `major`, `normal`, `high`) — call `get_issue_enums` to see all valid values and their localized labels; optional `handler` parameter accepts a username as alternative to `handler_id` (resolved against project members) |
102
92
  | `update_issue` | Update an existing issue |
103
93
  | `delete_issue` | Delete an issue |
104
94
 
@@ -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):
@@ -211,6 +222,11 @@ MANTIS_BASE_URL=... MANTIS_API_KEY=... TRANSPORT=http PORT=3456 node dist/index.
211
222
 
212
223
  Health check: `GET http://localhost:3456/health` (always public, no token required)
213
224
 
225
+ ## Documentation
226
+
227
+ - [**Cookbook**](docs/cookbook.md) — tool-oriented recipes with copy-paste-ready parameter examples for all registered tools
228
+ - [**Usage Examples**](docs/examples.md) — natural language prompt examples for everyday use cases (no tool names required)
229
+
214
230
  ## Development
215
231
 
216
232
  ```bash
package/dist/client.js CHANGED
@@ -10,20 +10,32 @@ export class MantisApiError extends Error {
10
10
  }
11
11
  }
12
12
  export class MantisClient {
13
- baseUrl;
14
- apiKey;
13
+ credentialFactory;
15
14
  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;
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
- buildUrl(path, params) {
26
- const url = new URL(`${this.baseUrl}/api/rest/${path}`);
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': this.apiKey,
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 response = await fetch(this.buildUrl(path), {
120
+ const { apiKey } = await this.getCredentials();
121
+ const response = await fetch(await this.buildUrl(path), {
108
122
  method: 'POST',
109
123
  headers: {
110
- 'Authorization': this.apiKey,
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
- const uploadDir = process.env.MANTIS_UPLOAD_DIR;
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/constants.js CHANGED
@@ -93,6 +93,18 @@ export const MANTIS_CANONICAL_ENUM_NAMES = {
93
93
  },
94
94
  };
95
95
  // ---------------------------------------------------------------------------
96
+ // Reverse lookup: canonical English enum name → numeric ID.
97
+ // Case-insensitive. Returns undefined when the name is not in the table.
98
+ // ---------------------------------------------------------------------------
99
+ export function resolveEnumId(group, name) {
100
+ if (!name)
101
+ return undefined;
102
+ const map = MANTIS_CANONICAL_ENUM_NAMES[group];
103
+ const lower = name.toLowerCase();
104
+ const entry = Object.entries(map).find(([, n]) => n.toLowerCase() === lower);
105
+ return entry ? Number(entry[0]) : undefined;
106
+ }
107
+ // ---------------------------------------------------------------------------
96
108
  // Issue enum config option names
97
109
  // ---------------------------------------------------------------------------
98
110
  export const ISSUE_ENUM_OPTIONS = [
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
- const config = await getConfig();
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(config.baseUrl, config.apiKey, (response) => versionHint.onSuccessfulResponse(response));
40
- const cache = new MetadataCache(config.cacheDir, config.cacheTtl);
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, config.uploadDir);
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 (config.search.enabled) {
68
+ if (startupConfig.search.enabled) {
59
69
  const { initializeSearchModule } = await import('./search/index.js');
60
- await initializeSearchModule(server, client, config.search);
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 config = await getConfig();
88
+ const startupConfig = await getStartupConfig();
79
89
  const server = await createMcpServer();
80
- const port = config.httpPort;
90
+ const port = startupConfig.httpPort;
81
91
  const httpServer = createServer(async (req, res) => {
82
92
  if (req.method === 'POST' && req.url === '/mcp') {
83
- if (config.httpToken) {
93
+ if (startupConfig.httpToken) {
84
94
  const auth = req.headers['authorization'];
85
- if (auth !== `Bearer ${config.httpToken}`) {
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, config.httpHost, () => {
120
- console.error(`MantisBT MCP Server v${version} running on http://${config.httpHost}:${port}/mcp`);
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
+ }