@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 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
  [![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
@@ -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
  [![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
@@ -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
- 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/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
+ }
@@ -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
+ }