@dpesch/mantisbt-mcp-server 1.8.2 → 1.9.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 +31 -0
- package/README.de.md +3 -3
- package/README.md +3 -3
- package/dist/client.js +7 -0
- package/dist/config.js +1 -0
- package/dist/date-filter.js +55 -0
- package/dist/index.js +1 -1
- package/dist/resources/index.js +14 -7
- package/dist/search/highlight.js +63 -0
- package/dist/search/store.js +4 -0
- package/dist/search/tools.js +65 -4
- package/dist/tools/config.js +23 -8
- package/dist/tools/issues.js +123 -18
- package/dist/tools/notes.js +1 -1
- package/docs/cookbook.de.md +64 -7
- package/docs/cookbook.md +64 -7
- package/docs/examples.de.md +12 -0
- package/docs/examples.md +12 -0
- package/package.json +1 -1
- package/server.json +2 -2
- package/tests/fixtures/get_issue.json +22 -0
- package/tests/helpers/search-mocks.ts +29 -6
- package/tests/search/highlight.test.ts +129 -0
- package/tests/search/tools.test.ts +258 -0
- package/tests/tools/issues.test.ts +446 -4
- package/tests/utils/date-filter.test.ts +169 -0
package/dist/tools/issues.js
CHANGED
|
@@ -1,20 +1,48 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { getVersionHint } from '../version-hint.js';
|
|
3
3
|
import { MANTIS_CANONICAL_ENUM_NAMES, MANTIS_RESOLVED_STATUS_ID, resolveEnumId } from '../constants.js';
|
|
4
|
+
import { dateFilterSchema, matchesDateFilter, hasDateFilter } from '../date-filter.js';
|
|
5
|
+
import { fetchIssueEnumsWithCache } from './config.js';
|
|
4
6
|
function errorText(msg) {
|
|
5
7
|
const vh = getVersionHint();
|
|
6
8
|
vh?.triggerLatestVersionFetch();
|
|
7
9
|
const hint = vh?.getUpdateHint();
|
|
8
10
|
return hint ? `Error: ${msg}\n\n${hint}` : `Error: ${msg}`;
|
|
9
11
|
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
const GET_ISSUES_CONCURRENCY = 5;
|
|
13
|
+
// Worker-pool: runs `fn` over all `items` with at most `concurrency` in-flight at once.
|
|
14
|
+
// nextIndex is only incremented inside microtasks, so the ++ is safe without a lock.
|
|
15
|
+
async function runWithConcurrency(items, concurrency, fn) {
|
|
16
|
+
const results = new Array(items.length);
|
|
17
|
+
let nextIndex = 0;
|
|
18
|
+
async function worker() {
|
|
19
|
+
while (nextIndex < items.length) {
|
|
20
|
+
const i = nextIndex++;
|
|
21
|
+
results[i] = await fn(items[i]);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
await Promise.all(Array.from({ length: Math.min(concurrency, items.length) }, () => worker()));
|
|
25
|
+
return results;
|
|
26
|
+
}
|
|
27
|
+
// Resolves an enum name (canonical or localized) to { id } or returns an error string.
|
|
28
|
+
async function resolveEnum(group, value, client) {
|
|
12
29
|
const id = resolveEnumId(group, value);
|
|
13
|
-
if (id
|
|
14
|
-
|
|
15
|
-
|
|
30
|
+
if (id !== undefined)
|
|
31
|
+
return { id };
|
|
32
|
+
try {
|
|
33
|
+
const enums = await fetchIssueEnumsWithCache(client);
|
|
34
|
+
const entries = enums[group] ?? [];
|
|
35
|
+
const lower = value.toLowerCase();
|
|
36
|
+
const entry = entries.find(e => e.name.toLowerCase() === lower ||
|
|
37
|
+
(e.label !== undefined && e.label.toLowerCase() === lower));
|
|
38
|
+
if (entry !== undefined)
|
|
39
|
+
return { id: entry.id };
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// localized lookup unavailable — fall through to static error
|
|
16
43
|
}
|
|
17
|
-
|
|
44
|
+
const valid = Object.values(MANTIS_CANONICAL_ENUM_NAMES[group]).join(', ');
|
|
45
|
+
return `Invalid ${group} "${value}". Valid canonical names: ${valid}. Call get_issue_enums to see localized labels.`;
|
|
18
46
|
}
|
|
19
47
|
export function registerIssueTools(server, client, cache) {
|
|
20
48
|
// ---------------------------------------------------------------------------
|
|
@@ -22,7 +50,7 @@ export function registerIssueTools(server, client, cache) {
|
|
|
22
50
|
// ---------------------------------------------------------------------------
|
|
23
51
|
server.registerTool('get_issue', {
|
|
24
52
|
title: 'Get Issue',
|
|
25
|
-
description: 'Retrieve a single MantisBT issue by its numeric ID. Returns all issue fields including notes, attachments, and relationships.',
|
|
53
|
+
description: 'Retrieve a single MantisBT issue by its numeric ID. Returns all issue fields including notes, attachments, and relationships. Notes are always included — no separate list_notes call needed.',
|
|
26
54
|
inputSchema: z.object({
|
|
27
55
|
id: z.coerce.number().int().positive().describe('Numeric issue ID'),
|
|
28
56
|
}),
|
|
@@ -45,11 +73,51 @@ export function registerIssueTools(server, client, cache) {
|
|
|
45
73
|
}
|
|
46
74
|
});
|
|
47
75
|
// ---------------------------------------------------------------------------
|
|
76
|
+
// get_issues
|
|
77
|
+
// ---------------------------------------------------------------------------
|
|
78
|
+
server.registerTool('get_issues', {
|
|
79
|
+
title: 'Get Multiple Issues',
|
|
80
|
+
description: 'Retrieve multiple MantisBT issues by their numeric IDs in a single MCP call. ' +
|
|
81
|
+
'Requests run in parallel (max 5 concurrent). ' +
|
|
82
|
+
'Missing or inaccessible IDs return null at their array position — ' +
|
|
83
|
+
'the call never fails due to individual missing IDs. ' +
|
|
84
|
+
'Response includes "requested", "found", and "failed" counters for quick validation.',
|
|
85
|
+
inputSchema: z.object({
|
|
86
|
+
ids: z
|
|
87
|
+
.array(z.coerce.number().int().positive())
|
|
88
|
+
.min(1)
|
|
89
|
+
.max(50)
|
|
90
|
+
.describe('Array of numeric issue IDs to fetch (1–50). null is returned per ID on 404/403/error instead of failing the whole call.'),
|
|
91
|
+
}),
|
|
92
|
+
annotations: {
|
|
93
|
+
readOnlyHint: true,
|
|
94
|
+
destructiveHint: false,
|
|
95
|
+
idempotentHint: true,
|
|
96
|
+
},
|
|
97
|
+
}, async ({ ids }) => {
|
|
98
|
+
const results = await runWithConcurrency(ids, GET_ISSUES_CONCURRENCY, async (id) => {
|
|
99
|
+
try {
|
|
100
|
+
const result = await client.get(`issues/${id}`);
|
|
101
|
+
return result.issues?.[0] ?? result;
|
|
102
|
+
}
|
|
103
|
+
catch {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
const found = results.filter((r) => r !== null).length;
|
|
108
|
+
return {
|
|
109
|
+
content: [{
|
|
110
|
+
type: 'text',
|
|
111
|
+
text: JSON.stringify({ issues: results, requested: ids.length, found, failed: ids.length - found }, null, 2),
|
|
112
|
+
}],
|
|
113
|
+
};
|
|
114
|
+
});
|
|
115
|
+
// ---------------------------------------------------------------------------
|
|
48
116
|
// list_issues
|
|
49
117
|
// ---------------------------------------------------------------------------
|
|
50
118
|
server.registerTool('list_issues', {
|
|
51
119
|
title: 'List Issues',
|
|
52
|
-
description: 'List MantisBT issues with optional filtering. Returns a paginated list of issues. Use the "select" parameter to limit returned fields and reduce response size significantly.\n\nNote: "assigned_to", "reporter_id",
|
|
120
|
+
description: 'List MantisBT issues with optional filtering. Returns a paginated list of issues. Use the "select" parameter to limit returned fields and reduce response size significantly.\n\nNote: "assigned_to", "reporter_id", "status", and date filters are applied client-side (the MantisBT REST API does not support these as server-side filters). When any of these filters are active the tool automatically fetches multiple pages internally until enough matching results are found (up to 500 issues scanned). The "page" and "page_size" parameters refer to the resulting filtered list.\n\nTip for date queries: fetching with select="id,updated_at,created_at" plus a date filter is very compact and efficient.',
|
|
53
121
|
inputSchema: z.object({
|
|
54
122
|
project_id: z.coerce.number().int().positive().optional().describe('Filter by project ID'),
|
|
55
123
|
page: z.coerce.number().int().positive().default(1).describe('Page number (default: 1)'),
|
|
@@ -61,13 +129,14 @@ export function registerIssueTools(server, client, cache) {
|
|
|
61
129
|
direction: z.enum(['ASC', 'DESC']).optional().describe('Sort direction'),
|
|
62
130
|
select: z.string().optional().describe('Comma-separated list of fields to include in the response (server-side projection). Significantly reduces response size. Example: "id,summary,status,priority,handler,updated_at"'),
|
|
63
131
|
status: z.string().optional().describe('Filter issues by status name (e.g. "new", "feedback", "acknowledged", "confirmed", "assigned", "resolved", "closed") or use "open" as shorthand for all statuses with id < 80 (i.e. not yet resolved or closed). Applied client-side after fetching — when combined with pagination, a page may contain fewer results than page_size.'),
|
|
132
|
+
...dateFilterSchema,
|
|
64
133
|
}),
|
|
65
134
|
annotations: {
|
|
66
135
|
readOnlyHint: true,
|
|
67
136
|
destructiveHint: false,
|
|
68
137
|
idempotentHint: true,
|
|
69
138
|
},
|
|
70
|
-
}, async ({ project_id, page, page_size, assigned_to, reporter_id, filter_id, sort, direction, select, status }) => {
|
|
139
|
+
}, async ({ project_id, page, page_size, assigned_to, reporter_id, filter_id, sort, direction, select, status, updated_after, updated_before, created_after, created_before }) => {
|
|
71
140
|
try {
|
|
72
141
|
const baseParams = {
|
|
73
142
|
project_id,
|
|
@@ -78,7 +147,8 @@ export function registerIssueTools(server, client, cache) {
|
|
|
78
147
|
direction,
|
|
79
148
|
select,
|
|
80
149
|
};
|
|
81
|
-
const
|
|
150
|
+
const dateFilter = { updated_after, updated_before, created_after, created_before };
|
|
151
|
+
const needsClientFilter = status !== undefined || assigned_to !== undefined || reporter_id !== undefined || hasDateFilter(dateFilter);
|
|
82
152
|
if (!needsClientFilter) {
|
|
83
153
|
// No client-side filtering — single API call, pass pagination as-is
|
|
84
154
|
const result = await client.get('issues', { ...baseParams, page, page_size });
|
|
@@ -95,6 +165,9 @@ export function registerIssueTools(server, client, cache) {
|
|
|
95
165
|
let serverPage = 1;
|
|
96
166
|
let hasMore = true;
|
|
97
167
|
const statusLower = status?.toLowerCase();
|
|
168
|
+
const statusId = status ? resolveEnumId('status', status) : undefined;
|
|
169
|
+
// Pre-parse date thresholds once — avoids repeated new Date() inside the scan loop
|
|
170
|
+
const updatedAfterMs = updated_after ? new Date(updated_after).getTime() : undefined;
|
|
98
171
|
while (matching.length < neededTotal && serverPage <= MAX_API_PAGES && hasMore) {
|
|
99
172
|
const batch = await client.get('issues', {
|
|
100
173
|
...baseParams,
|
|
@@ -103,6 +176,7 @@ export function registerIssueTools(server, client, cache) {
|
|
|
103
176
|
});
|
|
104
177
|
const issues = batch.issues ?? [];
|
|
105
178
|
hasMore = issues.length === API_PAGE_SIZE;
|
|
179
|
+
let stopAfterBatch = false;
|
|
106
180
|
for (const issue of issues) {
|
|
107
181
|
if (statusLower) {
|
|
108
182
|
if (!issue.status)
|
|
@@ -111,6 +185,10 @@ export function registerIssueTools(server, client, cache) {
|
|
|
111
185
|
if ((issue.status.id ?? 0) >= MANTIS_RESOLVED_STATUS_ID)
|
|
112
186
|
continue;
|
|
113
187
|
}
|
|
188
|
+
else if (statusId !== undefined) {
|
|
189
|
+
if (issue.status.id !== statusId)
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
114
192
|
else if (issue.status.name?.toLowerCase() !== statusLower) {
|
|
115
193
|
continue;
|
|
116
194
|
}
|
|
@@ -119,8 +197,20 @@ export function registerIssueTools(server, client, cache) {
|
|
|
119
197
|
continue;
|
|
120
198
|
if (reporter_id !== undefined && issue.reporter?.id !== reporter_id)
|
|
121
199
|
continue;
|
|
200
|
+
if (!matchesDateFilter(issue, dateFilter)) {
|
|
201
|
+
// MantisBT returns results newest-first. Once updated_at drops below
|
|
202
|
+
// updated_after, all subsequent pages are guaranteed to be older too.
|
|
203
|
+
// Finish the current batch first (items within it may still be newer),
|
|
204
|
+
// then stop fetching further pages.
|
|
205
|
+
if (updatedAfterMs && issue.updated_at && new Date(issue.updated_at).getTime() <= updatedAfterMs) {
|
|
206
|
+
stopAfterBatch = true;
|
|
207
|
+
}
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
122
210
|
matching.push(issue);
|
|
123
211
|
}
|
|
212
|
+
if (stopAfterBatch)
|
|
213
|
+
break;
|
|
124
214
|
serverPage++;
|
|
125
215
|
}
|
|
126
216
|
const start = (page - 1) * page_size;
|
|
@@ -147,8 +237,8 @@ export function registerIssueTools(server, client, cache) {
|
|
|
147
237
|
description: z.string().min(1).describe('Detailed issue description. Required — do not create issues without a description. Plain text or Markdown.'),
|
|
148
238
|
project_id: z.coerce.number().int().positive().describe('Project ID the issue belongs to'),
|
|
149
239
|
category: z.string().min(1).describe('Category name (use get_project_categories to list available categories)'),
|
|
150
|
-
priority: z.string().default('normal').describe('Priority
|
|
151
|
-
severity: z.string().default('minor').describe('Severity
|
|
240
|
+
priority: z.string().default('normal').describe('Priority: canonical English name (none, low, normal, high, urgent, immediate) or localized label. Default: "normal". Use get_issue_enums to see all available values.'),
|
|
241
|
+
severity: z.string().default('minor').describe('Severity: canonical English name (feature, trivial, text, tweak, minor, major, crash, block) or localized label. Default: "minor". Use get_issue_enums to see all available values.'),
|
|
152
242
|
handler_id: z.coerce.number().int().positive().optional().describe('User ID of the person to assign the issue to'),
|
|
153
243
|
handler: z.string().optional().describe('Username (login name) of the person to assign the issue to. Alternative to handler_id — the server resolves the name to a user ID from the project members. Use get_project_users to see available users.'),
|
|
154
244
|
version: z.string().optional().describe('Affected product version name (use get_project_versions to list available versions)'),
|
|
@@ -156,7 +246,7 @@ export function registerIssueTools(server, client, cache) {
|
|
|
156
246
|
fixed_in_version: z.string().optional().describe('Version name in which the issue was fixed (use get_project_versions to list available versions)'),
|
|
157
247
|
steps_to_reproduce: z.string().optional().describe('Steps to reproduce the issue. Plain text or Markdown.'),
|
|
158
248
|
additional_information: z.string().optional().describe('Additional information about the issue. Plain text or Markdown.'),
|
|
159
|
-
reproducibility: z.string().optional().describe('Reproducibility
|
|
249
|
+
reproducibility: z.string().optional().describe('Reproducibility: canonical English name or localized label (always, sometimes, random, have not tried, unable to reproduce, N/A). Use get_issue_enums to see all available values.'),
|
|
160
250
|
view_state: z.enum(['public', 'private']).optional().describe('Visibility of the issue: "public" (default) or "private"'),
|
|
161
251
|
}),
|
|
162
252
|
annotations: {
|
|
@@ -196,11 +286,11 @@ export function registerIssueTools(server, client, cache) {
|
|
|
196
286
|
project: { id: project_id },
|
|
197
287
|
category: { name: category },
|
|
198
288
|
};
|
|
199
|
-
const priorityResolved = resolveEnum('priority', priority);
|
|
289
|
+
const priorityResolved = await resolveEnum('priority', priority, client);
|
|
200
290
|
if (typeof priorityResolved === 'string')
|
|
201
291
|
return { content: [{ type: 'text', text: errorText(priorityResolved) }], isError: true };
|
|
202
292
|
body.priority = priorityResolved;
|
|
203
|
-
const severityResolved = resolveEnum('severity', severity);
|
|
293
|
+
const severityResolved = await resolveEnum('severity', severity, client);
|
|
204
294
|
if (typeof severityResolved === 'string')
|
|
205
295
|
return { content: [{ type: 'text', text: errorText(severityResolved) }], isError: true };
|
|
206
296
|
body.severity = severityResolved;
|
|
@@ -217,7 +307,7 @@ export function registerIssueTools(server, client, cache) {
|
|
|
217
307
|
if (additional_information !== undefined)
|
|
218
308
|
body.additional_information = additional_information;
|
|
219
309
|
if (reproducibility !== undefined) {
|
|
220
|
-
const reproducibilityResolved = resolveEnum('reproducibility', reproducibility);
|
|
310
|
+
const reproducibilityResolved = await resolveEnum('reproducibility', reproducibility, client);
|
|
221
311
|
if (typeof reproducibilityResolved === 'string')
|
|
222
312
|
return { content: [{ type: 'text', text: errorText(reproducibilityResolved) }], isError: true };
|
|
223
313
|
body.reproducibility = reproducibilityResolved;
|
|
@@ -279,6 +369,7 @@ The "fields" object accepts any combination of:
|
|
|
279
369
|
Important: when resolving an issue, always set BOTH status and resolution to avoid leaving resolution as "open".`,
|
|
280
370
|
inputSchema: z.object({
|
|
281
371
|
id: z.coerce.number().int().positive().describe('Numeric issue ID to update'),
|
|
372
|
+
dry_run: z.boolean().optional().describe('If true, return the patch payload that would be sent without actually updating the issue. Useful for previewing changes before committing them.'),
|
|
282
373
|
fields: z.object({
|
|
283
374
|
summary: z.string().optional(),
|
|
284
375
|
description: z.string().optional(),
|
|
@@ -302,9 +393,23 @@ Important: when resolving an issue, always set BOTH status and resolution to avo
|
|
|
302
393
|
destructiveHint: false,
|
|
303
394
|
idempotentHint: false,
|
|
304
395
|
},
|
|
305
|
-
}, async ({ id, fields }) => {
|
|
396
|
+
}, async ({ id, fields, dry_run }) => {
|
|
397
|
+
if (dry_run) {
|
|
398
|
+
return {
|
|
399
|
+
content: [{ type: 'text', text: JSON.stringify({ dry_run: true, id, would_patch: fields }, null, 2) }],
|
|
400
|
+
};
|
|
401
|
+
}
|
|
306
402
|
try {
|
|
307
|
-
const
|
|
403
|
+
const patch = { ...fields };
|
|
404
|
+
for (const field of ['status', 'priority', 'severity', 'resolution', 'reproducibility']) {
|
|
405
|
+
const val = fields[field];
|
|
406
|
+
if (val?.name !== undefined && val.id === undefined) {
|
|
407
|
+
const resolved = await resolveEnum(field, val.name, client);
|
|
408
|
+
if (typeof resolved !== 'string')
|
|
409
|
+
patch[field] = resolved;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
const result = await client.patch(`issues/${id}`, patch);
|
|
308
413
|
return {
|
|
309
414
|
content: [{ type: 'text', text: JSON.stringify(result.issue ?? result, null, 2) }],
|
|
310
415
|
};
|
package/dist/tools/notes.js
CHANGED
|
@@ -12,7 +12,7 @@ export function registerNoteTools(server, client) {
|
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
13
|
server.registerTool('list_notes', {
|
|
14
14
|
title: 'List Issue Notes',
|
|
15
|
-
description: 'List all notes (comments) attached to a MantisBT issue.',
|
|
15
|
+
description: 'List all notes (comments) attached to a MantisBT issue. Note: get_issue already includes notes in its response — use list_notes only when you need notes without fetching the full issue.',
|
|
16
16
|
inputSchema: z.object({
|
|
17
17
|
issue_id: z.coerce.number().int().positive().describe('Numeric issue ID'),
|
|
18
18
|
}),
|
package/docs/cookbook.de.md
CHANGED
|
@@ -145,7 +145,7 @@ Gibt die auf der eigenen MantisBT-Instanz konfigurierten Enum-Werte zurück. Vor
|
|
|
145
145
|
}
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
> **Hinweis:**
|
|
148
|
+
> **Hinweis:** Auf lokalisierten Instanzen liefert `get_issue_enums()` im Feld `name` den lokalisierten Begriff und optional im Feld `canonical_name` den englischen Originalnamen. `create_issue` akzeptiert **beides** — sowohl den kanonischen englischen Namen (z.B. `minor`) als auch den lokalisierten Namen (z.B. `Unschönheit`). Der Server löst den Wert automatisch auf.
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
@@ -347,6 +347,8 @@ Gibt nur Issues mit einem bestimmten Status zurück. Der Filter wird clientseiti
|
|
|
347
347
|
}
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
+
> **Hinweis:** Kanonische Statusnamen (z.B. `"new"`, `"resolved"`) werden zur numerischen ID aufgelöst und per `issue.status.id` gefiltert — funktioniert auch auf lokalisierten Installationen, bei denen die API übersetzte Statusnamen zurückgibt. Direkt übergebene lokalisierte Namen (z.B. `"Neu"`) werden als Fallback über den Namen abgeglichen. Das Kürzel `"open"` (alle Status mit id < 80) steht unabhängig von der Installationssprache immer zur Verfügung.
|
|
351
|
+
|
|
350
352
|
> **Hinweis:** Bei großen Projekten mit vielen Issues stattdessen einen vorgespeicherten MantisBT-Filter über `filter_id` verwenden — die clientseitige Filterung durchsucht nur die ersten 500 Issues (10 Seiten × 50).
|
|
351
353
|
|
|
352
354
|
---
|
|
@@ -456,8 +458,8 @@ Legt ein neues Issue in MantisBT an.
|
|
|
456
458
|
- `project_id` — numerische Projekt-ID
|
|
457
459
|
- `category` — Kategoriename als Zeichenkette
|
|
458
460
|
- `description` — _(optional)_ ausführliche Beschreibung
|
|
459
|
-
- `priority` — _(optional)_ kanonischer englischer
|
|
460
|
-
- `severity` — _(optional)_ kanonischer englischer
|
|
461
|
+
- `priority` — _(optional)_ Priorität: kanonischer englischer Name (`none`, `low`, `normal`, `high`, `urgent`, `immediate`) oder lokalisierter Begriff. Standard: `"normal"`. Alle verfügbaren Werte über `get_issue_enums()` ermitteln.
|
|
462
|
+
- `severity` — _(optional)_ Schweregrad: kanonischer englischer Name (`feature`, `trivial`, `text`, `tweak`, `minor`, `major`, `crash`, `block`) oder lokalisierter Begriff. Standard: `"minor"`. Alle verfügbaren Werte über `get_issue_enums()` ermitteln.
|
|
461
463
|
- `handler` — _(optional)_ Benutzername des Bearbeiters (wird automatisch in eine ID aufgelöst)
|
|
462
464
|
- `handler_id` — _(optional)_ numerische Benutzer-ID des Bearbeiters (Alternative zu `handler`)
|
|
463
465
|
|
|
@@ -500,11 +502,11 @@ Legt ein neues Issue in MantisBT an.
|
|
|
500
502
|
|
|
501
503
|
**Fehler: unbekannter Schweregrad oder unbekannte Priorität**
|
|
502
504
|
|
|
503
|
-
|
|
505
|
+
Der Server prüft zuerst kanonische englische Namen und fällt dann auf einen Live-`get_issue_enums`-Lookup zurück. Ein Fehler wird nur zurückgegeben, wenn der Wert weder kanonisch noch lokalisiert erkannt wird:
|
|
504
506
|
|
|
505
|
-
> Error: Invalid severity "
|
|
507
|
+
> Error: Invalid severity "xyz". Valid canonical names: feature, trivial, text, tweak, minor, major, crash, block. Call get_issue_enums to see localized labels.
|
|
506
508
|
|
|
507
|
-
Mit `get_issue_enums` lassen sich
|
|
509
|
+
Mit `get_issue_enums` lassen sich alle akzeptierten Werte ermitteln — sowohl kanonische als auch lokalisierte Namen funktionieren.
|
|
508
510
|
|
|
509
511
|
---
|
|
510
512
|
|
|
@@ -519,6 +521,8 @@ Löst ein Issue auf und schließt es. **Immer beide Felder** `status` und `resol
|
|
|
519
521
|
- `fields.status` — Status-Objekt mit Name
|
|
520
522
|
- `fields.resolution` — Auflösungs-Objekt mit ID
|
|
521
523
|
|
|
524
|
+
> **Hinweis:** Alle Enum-Felder (`status`, `priority`, `severity`, `resolution`, `reproducibility`) akzeptieren kanonische englische Namen, lokalisierte Namen oder numerische IDs. Der Server löst Namen automatisch zu IDs auf — dadurch ist die Übergabe sprachunabhängig.
|
|
525
|
+
|
|
522
526
|
**Request:**
|
|
523
527
|
|
|
524
528
|
```json
|
|
@@ -1246,6 +1250,7 @@ Findet Issues, die einem natürlichsprachigen Suchbegriff semantisch ähnlich si
|
|
|
1246
1250
|
**Parameter:**
|
|
1247
1251
|
- `query` — Suchanfrage in natürlicher Sprache
|
|
1248
1252
|
- `top_n` — _(optional)_ Anzahl zurückzugebender Ergebnisse; Standard 10
|
|
1253
|
+
- `highlight` — _(optional)_ bei `true` werden keyword-basierte Ausschnitte je Ergebnis ergänzt; Standard `false`
|
|
1249
1254
|
|
|
1250
1255
|
**Request:**
|
|
1251
1256
|
|
|
@@ -1280,6 +1285,7 @@ Reichert Suchergebnisse mit bestimmten Feldern aus MantisBT an. Ohne `select` we
|
|
|
1280
1285
|
- `query` — Suchanfrage in natürlicher Sprache
|
|
1281
1286
|
- `top_n` — _(optional)_ Anzahl der Ergebnisse
|
|
1282
1287
|
- `select` — kommagetrennte Feldnamen, die für jedes Ergebnis abgerufen werden
|
|
1288
|
+
- `highlight` — _(optional)_ bei `true` werden keyword-basierte Ausschnitte je Ergebnis ergänzt; Standard `false`
|
|
1283
1289
|
|
|
1284
1290
|
**Request:**
|
|
1285
1291
|
|
|
@@ -1310,6 +1316,57 @@ Reichert Suchergebnisse mit bestimmten Feldern aus MantisBT an. Ohne `select` we
|
|
|
1310
1316
|
|
|
1311
1317
|
---
|
|
1312
1318
|
|
|
1319
|
+
### Suche mit Keyword-Highlights
|
|
1320
|
+
|
|
1321
|
+
Zeigt, welcher Teil eines Issues mit der Suchanfrage übereinstimmt. Jedes Ergebnis, das lexikalisch mit der Anfrage übereinstimmt, erhält ein `highlights`-Feld mit fett hervorgehobenen Ausschnitten. Highlights sind keyword-basiert (lexikalisch), nicht semantisch — Ergebnisse ohne lexikalische Übereinstimmung haben kein `highlights`-Feld.
|
|
1322
|
+
|
|
1323
|
+
**Tool:** `search_issues`
|
|
1324
|
+
|
|
1325
|
+
**Parameter:**
|
|
1326
|
+
- `query` — Suchanfrage in natürlicher Sprache
|
|
1327
|
+
- `top_n` — _(optional)_ Anzahl der Ergebnisse; Standard 10
|
|
1328
|
+
- `highlight` — auf `true` setzen, um Highlights zu aktivieren
|
|
1329
|
+
|
|
1330
|
+
**Request:**
|
|
1331
|
+
|
|
1332
|
+
```json
|
|
1333
|
+
{
|
|
1334
|
+
"query": "Login-Fehler nach Passwort-Reset",
|
|
1335
|
+
"top_n": 5,
|
|
1336
|
+
"highlight": true
|
|
1337
|
+
}
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
**Response:**
|
|
1341
|
+
|
|
1342
|
+
```json
|
|
1343
|
+
[
|
|
1344
|
+
{
|
|
1345
|
+
"id": 1042,
|
|
1346
|
+
"score": 0.91,
|
|
1347
|
+
"highlights": {
|
|
1348
|
+
"summary": "**Login**-Button reagiert nach **Passwort**-**Reset** auf Mobile Safari nicht",
|
|
1349
|
+
"description": "…Benutzer tippt auf **Login** und es passiert nichts. Reproduzierbar nach einem **Passwort**-**Reset**-Vorgang…"
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
"id": 987,
|
|
1354
|
+
"score": 0.84,
|
|
1355
|
+
"highlights": {
|
|
1356
|
+
"summary": "**Login** schlägt mit 401 fehl — Token ungültig"
|
|
1357
|
+
}
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
"id": 1015,
|
|
1361
|
+
"score": 0.79
|
|
1362
|
+
}
|
|
1363
|
+
]
|
|
1364
|
+
```
|
|
1365
|
+
|
|
1366
|
+
> **Hinweis:** Nur Ergebnisse mit mindestens einer Keyword-Übereinstimmung in `summary` oder `description` erhalten ein `highlights`-Feld. Der `description`-Ausschnitt umfasst ca. 300 Zeichen, zentriert um den ersten Treffer. Wenn zusätzlich `select` gesetzt ist und `summary` oder `description` enthält, werden die Highlights aus den abgerufenen Feldern generiert; andernfalls stammen sie aus den indizierten Metadaten.
|
|
1367
|
+
|
|
1368
|
+
---
|
|
1369
|
+
|
|
1313
1370
|
## Projekte & Kategorien
|
|
1314
1371
|
|
|
1315
1372
|
### Projektkategorien auflisten
|
|
@@ -1635,7 +1692,7 @@ Gibt eine kombinierte Ansicht eines einzelnen Projekts zurück: Projektfelder so
|
|
|
1635
1692
|
|
|
1636
1693
|
### Issue-Enum-Werte lesen
|
|
1637
1694
|
|
|
1638
|
-
Gibt gültige ID/Name-Paare für alle Issue-Enum-Felder zurück (Severity, Priority, Status, Resolution, Reproducibility).
|
|
1695
|
+
Gibt gültige ID/Name-Paare für alle Issue-Enum-Felder zurück (Severity, Priority, Status, Resolution, Reproducibility). Bei `create_issue` werden sowohl kanonische englische Namen als auch lokalisierte `name`/`label`-Werte akzeptiert — diese Ressource hilft, alle verfügbaren Werte zu ermitteln.
|
|
1639
1696
|
|
|
1640
1697
|
**Ressource-URI:** `mantis://enums`
|
|
1641
1698
|
|
package/docs/cookbook.md
CHANGED
|
@@ -145,7 +145,7 @@ Returns the enum values configured on your specific MantisBT instance. Use this
|
|
|
145
145
|
}
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
-
> **Note:**
|
|
148
|
+
> **Note:** On localized installations, `get_issue_enums()` returns a `name` in the local language and an optional `canonical_name` with the English original. `create_issue` accepts **both** — pass either the canonical English name (e.g. `minor`) or the localized name (e.g. `Unschönheit`). The server resolves the value automatically.
|
|
149
149
|
|
|
150
150
|
---
|
|
151
151
|
|
|
@@ -347,6 +347,8 @@ Returns only issues with a specific status. The filter is applied client-side
|
|
|
347
347
|
}
|
|
348
348
|
```
|
|
349
349
|
|
|
350
|
+
> **Note:** Canonical status names (e.g. `"new"`, `"resolved"`) are resolved to their numeric ID and matched by `issue.status.id` — this works correctly even on localized installations where the API returns translated status names. Localized names passed directly (e.g. `"Neu"`) are matched by name as a fallback. The `"open"` shorthand (all statuses with id < 80) is always available regardless of installation language.
|
|
351
|
+
|
|
350
352
|
> **Note:** For large projects with many issues, use a pre-saved MantisBT filter via `filter_id` instead — client-side filtering only scans the first 500 issues (10 pages × 50).
|
|
351
353
|
|
|
352
354
|
---
|
|
@@ -456,8 +458,8 @@ Creates a new issue in MantisBT.
|
|
|
456
458
|
- `project_id` — numeric project ID
|
|
457
459
|
- `category` — category name string
|
|
458
460
|
- `description` — _(optional)_ detailed description
|
|
459
|
-
- `priority` — _(optional)_ canonical English
|
|
460
|
-
- `severity` — _(optional)_ canonical English
|
|
461
|
+
- `priority` — _(optional)_ priority: canonical English name (`none`, `low`, `normal`, `high`, `urgent`, `immediate`) or localized label. Default: `"normal"`. Use `get_issue_enums()` to see all available values.
|
|
462
|
+
- `severity` — _(optional)_ severity: canonical English name (`feature`, `trivial`, `text`, `tweak`, `minor`, `major`, `crash`, `block`) or localized label. Default: `"minor"`. Use `get_issue_enums()` to see all available values.
|
|
461
463
|
- `handler` — _(optional)_ assignee username (resolved to ID automatically)
|
|
462
464
|
- `handler_id` — _(optional)_ assignee numeric user ID (alternative to `handler`)
|
|
463
465
|
|
|
@@ -500,11 +502,11 @@ Creates a new issue in MantisBT.
|
|
|
500
502
|
|
|
501
503
|
**Error: unknown severity or priority**
|
|
502
504
|
|
|
503
|
-
|
|
505
|
+
The server first checks canonical English names, then falls back to a live `get_issue_enums` lookup. An error is only returned if the value matches neither:
|
|
504
506
|
|
|
505
|
-
> Error: Invalid severity "
|
|
507
|
+
> Error: Invalid severity "xyz". Valid canonical names: feature, trivial, text, tweak, minor, major, crash, block. Call get_issue_enums to see localized labels.
|
|
506
508
|
|
|
507
|
-
Use `get_issue_enums` to discover
|
|
509
|
+
Use `get_issue_enums` to discover all accepted values — both canonical and localized names work.
|
|
508
510
|
|
|
509
511
|
---
|
|
510
512
|
|
|
@@ -519,6 +521,8 @@ Resolves and closes an issue. Always set **both** `status` and `resolution` —
|
|
|
519
521
|
- `fields.status` — status object with name
|
|
520
522
|
- `fields.resolution` — resolution object with id
|
|
521
523
|
|
|
524
|
+
> **Note:** All enum fields (`status`, `priority`, `severity`, `resolution`, `reproducibility`) accept the canonical English name, a localized name, or a numeric `id`. The server resolves names to IDs automatically — IDs are always sent to the API, ensuring language-independence.
|
|
525
|
+
|
|
522
526
|
**Request:**
|
|
523
527
|
|
|
524
528
|
```json
|
|
@@ -1246,6 +1250,7 @@ Finds issues semantically similar to a natural language query. Returns issue IDs
|
|
|
1246
1250
|
**Parameters:**
|
|
1247
1251
|
- `query` — natural language search query
|
|
1248
1252
|
- `top_n` — _(optional)_ number of results to return; default 10
|
|
1253
|
+
- `highlight` — _(optional)_ when `true`, adds keyword-matched excerpts to each result; default `false`
|
|
1249
1254
|
|
|
1250
1255
|
**Request:**
|
|
1251
1256
|
|
|
@@ -1280,6 +1285,7 @@ Enriches search results with specific fields fetched from MantisBT. Without `sel
|
|
|
1280
1285
|
- `query` — natural language search query
|
|
1281
1286
|
- `top_n` — _(optional)_ number of results
|
|
1282
1287
|
- `select` — comma-separated field names to fetch for each result
|
|
1288
|
+
- `highlight` — _(optional)_ when `true`, adds keyword-matched excerpts to each result; default `false`
|
|
1283
1289
|
|
|
1284
1290
|
**Request:**
|
|
1285
1291
|
|
|
@@ -1310,6 +1316,57 @@ Enriches search results with specific fields fetched from MantisBT. Without `sel
|
|
|
1310
1316
|
|
|
1311
1317
|
---
|
|
1312
1318
|
|
|
1319
|
+
### Search with keyword highlights
|
|
1320
|
+
|
|
1321
|
+
Shows which part of an issue matched the search query. Each result that has keyword overlap with the query receives a `highlights` field with bold-marked excerpts. Highlights are keyword-based (lexical), not semantic — results with no lexical overlap will not have a `highlights` field.
|
|
1322
|
+
|
|
1323
|
+
**Tool:** `search_issues`
|
|
1324
|
+
|
|
1325
|
+
**Parameters:**
|
|
1326
|
+
- `query` — natural language search query
|
|
1327
|
+
- `top_n` — _(optional)_ number of results; default 10
|
|
1328
|
+
- `highlight` — set to `true` to enable highlights
|
|
1329
|
+
|
|
1330
|
+
**Request:**
|
|
1331
|
+
|
|
1332
|
+
```json
|
|
1333
|
+
{
|
|
1334
|
+
"query": "login error after password reset",
|
|
1335
|
+
"top_n": 5,
|
|
1336
|
+
"highlight": true
|
|
1337
|
+
}
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
**Response:**
|
|
1341
|
+
|
|
1342
|
+
```json
|
|
1343
|
+
[
|
|
1344
|
+
{
|
|
1345
|
+
"id": 1042,
|
|
1346
|
+
"score": 0.91,
|
|
1347
|
+
"highlights": {
|
|
1348
|
+
"summary": "**Login** button unresponsive after **password** **reset** on mobile Safari",
|
|
1349
|
+
"description": "…user taps **login** and nothing happens. Reproducible after a **password** **reset** flow…"
|
|
1350
|
+
}
|
|
1351
|
+
},
|
|
1352
|
+
{
|
|
1353
|
+
"id": 987,
|
|
1354
|
+
"score": 0.84,
|
|
1355
|
+
"highlights": {
|
|
1356
|
+
"summary": "**Login** fails with 401 — token invalidated"
|
|
1357
|
+
}
|
|
1358
|
+
},
|
|
1359
|
+
{
|
|
1360
|
+
"id": 1015,
|
|
1361
|
+
"score": 0.79
|
|
1362
|
+
}
|
|
1363
|
+
]
|
|
1364
|
+
```
|
|
1365
|
+
|
|
1366
|
+
> **Note:** Only results with at least one keyword match in `summary` or `description` receive a `highlights` field. The `description` excerpt is approximately 300 characters, centred around the first match. When `select` is also set and includes `summary` or `description`, highlights are generated from the fetched fields; otherwise they come from the indexed metadata.
|
|
1367
|
+
|
|
1368
|
+
---
|
|
1369
|
+
|
|
1313
1370
|
## Projects & Categories
|
|
1314
1371
|
|
|
1315
1372
|
### List project categories
|
|
@@ -1635,7 +1692,7 @@ Returns a combined view of a single project: its fields plus all members, versio
|
|
|
1635
1692
|
|
|
1636
1693
|
### Read issue enum values
|
|
1637
1694
|
|
|
1638
|
-
Returns valid ID/name pairs for all issue enum fields (severity, priority, status, resolution, reproducibility).
|
|
1695
|
+
Returns valid ID/name pairs for all issue enum fields (severity, priority, status, resolution, reproducibility). For `create_issue`, both the canonical English name and the localized `name`/`label` are accepted — this resource helps discover all available values.
|
|
1639
1696
|
|
|
1640
1697
|
**Resource URI:** `mantis://enums`
|
|
1641
1698
|
|
package/docs/examples.de.md
CHANGED
|
@@ -155,6 +155,18 @@ Die semantische Suche versteht die *Bedeutung* deiner Frage — nicht nur einzel
|
|
|
155
155
|
|
|
156
156
|
---
|
|
157
157
|
|
|
158
|
+
### Keyword-Highlights
|
|
159
|
+
|
|
160
|
+
> »Suche nach Login-Fehlern und zeige mir, welche Stelle im Ticket relevant ist.«
|
|
161
|
+
|
|
162
|
+
> »Finde Issues zum Passwort-Reset-Ablauf und hebe die passenden Stellen hervor, damit ich die Ergebnisse schnell überfliegen kann.«
|
|
163
|
+
|
|
164
|
+
> »Suche nach 'Rechnungsexport' und markiere die relevanten Ausschnitte in Titel und Beschreibung.«
|
|
165
|
+
|
|
166
|
+
Exakte Parameter und Response-Shape: [Cookbook — Suche mit Keyword-Highlights](cookbook.de.md#suche-mit-keyword-highlights).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
158
170
|
### Unscharfe / terminologieunabhängige Suche
|
|
159
171
|
|
|
160
172
|
> »Finde Issues zu 'doppelten Einträgen' — sie könnten auch als 'zweimal angezeigt', 'doppelte Datensätze' oder 'Phantom-Zeilen' beschrieben sein.«
|
package/docs/examples.md
CHANGED
|
@@ -155,6 +155,18 @@ Semantic search understands the *meaning* of your question — not just keywords
|
|
|
155
155
|
|
|
156
156
|
---
|
|
157
157
|
|
|
158
|
+
### Keyword highlights
|
|
159
|
+
|
|
160
|
+
> "Search for login errors and show me which part of each ticket is relevant."
|
|
161
|
+
|
|
162
|
+
> "Find issues about the password reset flow and highlight the matching passages so I can quickly scan the results."
|
|
163
|
+
|
|
164
|
+
> "Search for 'invoice export' and highlight the relevant excerpts in summary and description."
|
|
165
|
+
|
|
166
|
+
For exact parameters and response shape, see the [Cookbook — Search with keyword highlights](cookbook.md#search-with-keyword-highlights).
|
|
167
|
+
|
|
168
|
+
---
|
|
169
|
+
|
|
158
170
|
### Fuzzy / terminology-independent search
|
|
159
171
|
|
|
160
172
|
> "Find issues about 'duplicate entries' — they might also be described as 'shown twice', 'double records', or 'phantom rows'."
|
package/package.json
CHANGED
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.
|
|
6
|
+
"version": "1.9.0",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@dpesch/mantisbt-mcp-server",
|
|
11
|
-
"version": "1.
|
|
11
|
+
"version": "1.9.0",
|
|
12
12
|
"runtimeHint": "npx",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
@@ -58,6 +58,28 @@
|
|
|
58
58
|
"sticky": false,
|
|
59
59
|
"created_at": "2026-03-16T15:40:38+01:00",
|
|
60
60
|
"updated_at": "2026-03-16T15:40:38+01:00",
|
|
61
|
+
"notes": [
|
|
62
|
+
{
|
|
63
|
+
"id": 1001,
|
|
64
|
+
"reporter": { "id": 51, "name": "user_1" },
|
|
65
|
+
"text": "First note on this issue.",
|
|
66
|
+
"view_state": { "id": 10, "name": "public", "label": "öffentlich" },
|
|
67
|
+
"attachments": [],
|
|
68
|
+
"type": "note",
|
|
69
|
+
"created_at": "2026-03-16T16:00:00+01:00",
|
|
70
|
+
"updated_at": "2026-03-16T16:00:00+01:00"
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"id": 1002,
|
|
74
|
+
"reporter": { "id": 51, "name": "user_1" },
|
|
75
|
+
"text": "Second note on this issue.",
|
|
76
|
+
"view_state": { "id": 10, "name": "public", "label": "öffentlich" },
|
|
77
|
+
"attachments": [],
|
|
78
|
+
"type": "note",
|
|
79
|
+
"created_at": "2026-03-16T17:00:00+01:00",
|
|
80
|
+
"updated_at": "2026-03-16T17:00:00+01:00"
|
|
81
|
+
}
|
|
82
|
+
],
|
|
61
83
|
"history": [
|
|
62
84
|
{
|
|
63
85
|
"created_at": "2026-03-16T15:40:38+01:00",
|