@dpesch/mantisbt-mcp-server 1.8.0 → 1.8.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
@@ -11,6 +11,13 @@ This project adheres to [Semantic Versioning](https://semver.org/).
11
11
 
12
12
  ---
13
13
 
14
+ ## [1.8.1] – 2026-03-27
15
+
16
+ ### Fixed
17
+ - `MANTIS_BASE_URL` values ending in `/api/rest` (as shown in README examples) caused a doubled path (`/api/rest/api/rest/...`) and broke all API calls. A new `normalizeBaseUrl()` helper strips the `/api/rest` suffix (and any trailing slash) on startup. Both formats are now accepted: `https://your-mantis.example.com` and `https://your-mantis.example.com/api/rest`.
18
+
19
+ ---
20
+
14
21
  ## [1.8.0] – 2026-03-27
15
22
 
16
23
  ### Added
package/README.de.md CHANGED
@@ -65,7 +65,7 @@ npm run build
65
65
 
66
66
  | Variable | Pflicht | Standard | Beschreibung |
67
67
  |---|---|---|---|
68
- | `MANTIS_BASE_URL` | ✅ | – | Basis-URL der MantisBT REST API |
68
+ | `MANTIS_BASE_URL` | ✅ | – | Basis-URL der MantisBT-Installation. Beide Formate werden akzeptiert: `https://deine-mantis-instanz.example.com` und `https://deine-mantis-instanz.example.com/api/rest` — das `/api/rest`-Suffix wird automatisch normalisiert. |
69
69
  | `MANTIS_API_KEY` | ✅ | – | API-Token für die Authentifizierung |
70
70
  | `MANTIS_CACHE_DIR` | – | `~/.cache/mantisbt-mcp` | Verzeichnis für den Metadaten-Cache |
71
71
  | `MANTIS_CACHE_TTL` | – | `3600` | Cache-Lebensdauer in Sekunden |
package/README.md CHANGED
@@ -65,7 +65,7 @@ npm run build
65
65
 
66
66
  | Variable | Required | Default | Description |
67
67
  |---|---|---|---|
68
- | `MANTIS_BASE_URL` | ✅ | – | Base URL of the MantisBT REST API |
68
+ | `MANTIS_BASE_URL` | ✅ | – | Base URL of your MantisBT installation. Both `https://your-mantis.example.com` and `https://your-mantis.example.com/api/rest` are accepted — the `/api/rest` suffix is normalized automatically. |
69
69
  | `MANTIS_API_KEY` | ✅ | – | API token for authentication |
70
70
  | `MANTIS_CACHE_DIR` | – | `~/.cache/mantisbt-mcp` | Directory for the metadata cache |
71
71
  | `MANTIS_CACHE_TTL` | – | `3600` | Cache lifetime in seconds |
package/dist/client.js CHANGED
@@ -1,4 +1,16 @@
1
1
  // ---------------------------------------------------------------------------
2
+ // Helpers
3
+ // ---------------------------------------------------------------------------
4
+ /**
5
+ * Normalise a MANTIS_BASE_URL so that it never ends with "/api/rest" or a
6
+ * trailing slash. The client always appends "/api/rest/<path>" itself, so
7
+ * users who follow README examples that include "/api/rest" in the URL must
8
+ * not end up with a doubled prefix.
9
+ */
10
+ export function normalizeBaseUrl(url) {
11
+ return url.replace(/\/api\/rest\/?$/, '').replace(/\/$/, '');
12
+ }
13
+ // ---------------------------------------------------------------------------
2
14
  // MantisApiError
3
15
  // ---------------------------------------------------------------------------
4
16
  export class MantisApiError extends Error {
@@ -15,7 +27,7 @@ export class MantisClient {
15
27
  resolvedCredentials;
16
28
  constructor(baseUrlOrFactory, apiKeyOrObserver, responseObserver) {
17
29
  if (typeof baseUrlOrFactory === 'string') {
18
- this.resolvedCredentials = { baseUrl: baseUrlOrFactory.replace(/\/$/, ''), apiKey: apiKeyOrObserver };
30
+ this.resolvedCredentials = { baseUrl: normalizeBaseUrl(baseUrlOrFactory), apiKey: apiKeyOrObserver };
19
31
  this.responseObserver = responseObserver;
20
32
  }
21
33
  else {
@@ -29,7 +41,7 @@ export class MantisClient {
29
41
  async getCredentials() {
30
42
  if (!this.resolvedCredentials) {
31
43
  const { baseUrl, apiKey } = await this.credentialFactory();
32
- this.resolvedCredentials = { baseUrl: baseUrl.replace(/\/$/, ''), apiKey };
44
+ this.resolvedCredentials = { baseUrl: normalizeBaseUrl(baseUrl), apiKey };
33
45
  }
34
46
  return this.resolvedCredentials;
35
47
  }
package/dist/config.js CHANGED
@@ -2,6 +2,7 @@ import { readFile } from 'node:fs/promises';
2
2
  import { homedir } from 'node:os';
3
3
  import { join, dirname } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
+ import { normalizeBaseUrl } from './client.js';
5
6
  // ---------------------------------------------------------------------------
6
7
  // .env.local loader
7
8
  // ---------------------------------------------------------------------------
@@ -85,7 +86,7 @@ export async function getConfig() {
85
86
  `Set the environment variables MANTIS_BASE_URL and MANTIS_API_KEY.`);
86
87
  }
87
88
  cachedConfig = {
88
- baseUrl: baseUrl.replace(/\/$/, ''), // strip trailing slash
89
+ baseUrl: normalizeBaseUrl(baseUrl),
89
90
  apiKey,
90
91
  ...readNonCredentialConfig(),
91
92
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dpesch/mantisbt-mcp-server",
3
- "version": "1.8.0",
3
+ "version": "1.8.1",
4
4
  "mcpName": "io.github.dpesch/mantisbt-mcp-server",
5
5
  "description": "MCP server for MantisBT REST API – read and manage bug tracker issues",
6
6
  "author": "Dominik Pesch",
@@ -10,7 +10,8 @@
10
10
  // 3. Filters every commit between anchor and tip in order (oldest first)
11
11
  // 4. Builds a proper filtered chain anchored to the actual remote tip
12
12
  // 5. Pushes the filtered tip with --force
13
- // 6. Exits 1 to block git's unfiltered push
13
+ // 6. Exits 2 to block git's unfiltered push
14
+ // Exit codes: 0 = no Codeberg refs, 1 = error, 2 = success (filtered push done)
14
15
  //
15
16
  // Branches are processed before tags so the shaMap is available for tags
16
17
  // that point to commits already processed as part of the branch.
@@ -199,5 +200,5 @@ rl.on('close', () => {
199
200
 
200
201
  if (pushed === 0) process.exit(0);
201
202
  console.log('→ Filtered push complete. Blocking unfiltered push.');
202
- process.exit(1);
203
+ process.exit(2); // 2 = success (filtered push done, unfiltered blocked); 1 = error
203
204
  });
package/server.json CHANGED
@@ -3,12 +3,12 @@
3
3
  "name": "io.github.dpesch/mantisbt-mcp-server",
4
4
  "title": "MantisBT MCP Server",
5
5
  "description": "MantisBT MCP server – manage issues, notes, files, tags, and relationships. With semantic search.",
6
- "version": "1.8.0",
6
+ "version": "1.8.1",
7
7
  "packages": [
8
8
  {
9
9
  "registryType": "npm",
10
10
  "identifier": "@dpesch/mantisbt-mcp-server",
11
- "version": "1.8.0",
11
+ "version": "1.8.1",
12
12
  "runtimeHint": "npx",
13
13
  "transport": {
14
14
  "type": "stdio"
@@ -56,6 +56,28 @@ describe('MantisClient – URL building', () => {
56
56
  });
57
57
  });
58
58
 
59
+ it('strips /api/rest suffix when user includes it in the base URL', () => {
60
+ const fetchMock = vi.fn().mockResolvedValue(makeResponse(200, '{}'));
61
+ vi.stubGlobal('fetch', fetchMock);
62
+
63
+ const client = new MantisClient('https://mantis.example.com/api/rest', 'token123');
64
+ return client.get('issues/42').then(() => {
65
+ const calledUrl: string = fetchMock.mock.calls[0][0] as string;
66
+ expect(calledUrl).toBe('https://mantis.example.com/api/rest/issues/42');
67
+ });
68
+ });
69
+
70
+ it('strips /api/rest/ (with trailing slash) from base URL', () => {
71
+ const fetchMock = vi.fn().mockResolvedValue(makeResponse(200, '{}'));
72
+ vi.stubGlobal('fetch', fetchMock);
73
+
74
+ const client = new MantisClient('https://mantis.example.com/api/rest/', 'token123');
75
+ return client.get('issues/42').then(() => {
76
+ const calledUrl: string = fetchMock.mock.calls[0][0] as string;
77
+ expect(calledUrl).toBe('https://mantis.example.com/api/rest/issues/42');
78
+ });
79
+ });
80
+
59
81
  it('appends defined query parameters', () => {
60
82
  const fetchMock = vi.fn().mockResolvedValue(makeResponse(200, '{}'));
61
83
  vi.stubGlobal('fetch', fetchMock);
@@ -40,24 +40,44 @@ beforeEach(() => {
40
40
 
41
41
  describe('getConfig() – ENV variables', () => {
42
42
  it('reads baseUrl and apiKey from environment variables', async () => {
43
- vi.stubEnv('MANTIS_BASE_URL', 'https://mantis.example.com/api/rest');
43
+ vi.stubEnv('MANTIS_BASE_URL', 'https://mantis.example.com');
44
44
  vi.stubEnv('MANTIS_API_KEY', 'env-api-key');
45
45
 
46
46
  const getConfig = await freshGetConfig();
47
47
  const config = await getConfig();
48
48
 
49
- expect(config.baseUrl).toBe('https://mantis.example.com/api/rest');
49
+ expect(config.baseUrl).toBe('https://mantis.example.com');
50
50
  expect(config.apiKey).toBe('env-api-key');
51
51
  });
52
52
 
53
53
  it('strips trailing slash from baseUrl', async () => {
54
+ vi.stubEnv('MANTIS_BASE_URL', 'https://mantis.example.com/');
55
+ vi.stubEnv('MANTIS_API_KEY', 'key');
56
+
57
+ const getConfig = await freshGetConfig();
58
+ const config = await getConfig();
59
+
60
+ expect(config.baseUrl).toBe('https://mantis.example.com');
61
+ });
62
+
63
+ it('strips /api/rest suffix from baseUrl when user includes it', async () => {
64
+ vi.stubEnv('MANTIS_BASE_URL', 'https://mantis.example.com/api/rest');
65
+ vi.stubEnv('MANTIS_API_KEY', 'key');
66
+
67
+ const getConfig = await freshGetConfig();
68
+ const config = await getConfig();
69
+
70
+ expect(config.baseUrl).toBe('https://mantis.example.com');
71
+ });
72
+
73
+ it('strips /api/rest/ (with trailing slash) from baseUrl', async () => {
54
74
  vi.stubEnv('MANTIS_BASE_URL', 'https://mantis.example.com/api/rest/');
55
75
  vi.stubEnv('MANTIS_API_KEY', 'key');
56
76
 
57
77
  const getConfig = await freshGetConfig();
58
78
  const config = await getConfig();
59
79
 
60
- expect(config.baseUrl).toBe('https://mantis.example.com/api/rest');
80
+ expect(config.baseUrl).toBe('https://mantis.example.com');
61
81
  });
62
82
 
63
83
  it('parses MANTIS_CACHE_TTL as a number', async () => {