@dpesch/mantisbt-mcp-server 1.4.0 → 1.5.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/.env.local +3 -0
- package/CHANGELOG.md +11 -0
- package/README.de.md +4 -4
- package/README.md +4 -4
- package/dist/constants.js +77 -0
- package/dist/index.js +1 -1
- package/dist/search/tools.js +27 -2
- package/dist/tools/config.js +16 -2
- package/dist/tools/issues.js +46 -6
- package/dist/tools/relationships.js +27 -11
- package/package.json +1 -1
- package/tests/fixtures/recorded/get_current_user.json +108 -0
- package/tests/fixtures/recorded/get_issue.json +138 -0
- package/tests/fixtures/recorded/get_issue_enums.json +67 -0
- package/tests/fixtures/recorded/get_issue_fields_sample.json +138 -0
- package/tests/fixtures/recorded/get_project_categories.json +241 -0
- package/tests/fixtures/recorded/get_project_versions.json +28 -0
- package/tests/fixtures/recorded/list_issues.json +463 -0
- package/tests/fixtures/recorded/list_projects.json +10641 -0
- package/tests/search/tools.test.ts +117 -0
- package/tests/tools/config.test.ts +71 -2
- package/tests/tools/issues.test.ts +156 -1
- package/tests/tools/relationships.test.ts +75 -0
- package/tests/tools/string-coercion.test.ts +3 -1
package/.env.local
ADDED
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,17 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.5.0] – 2026-03-17
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `create_issue`: always returns the complete issue object. Older MantisBT versions returned only `{ id: N }` on `POST /issues`; the tool now detects this and performs an automatic `GET /issues/{id}` to retrieve the full issue. If that fetch fails the minimal object is returned instead (the issue was already created).
|
|
14
|
+
- `get_issue_enums`: each entry now includes an optional `canonical_name` field containing the standard English API name (e.g. `"minor"`, `"block"`) when the returned `name` differs from it. This occurs on localized MantisBT installations where enum values have been customized at the database level (e.g. `name: "kleinerer Fehler"` instead of `"minor"`). The field is omitted when `name` already matches the canonical value and for custom entries without a known canonical name. Available for all five enum groups (severity, status, priority, resolution, reproducibility).
|
|
15
|
+
- `create_issue`: new optional `handler` parameter accepts a username string as alternative to `handler_id`. The server resolves the name against the project members list (from the metadata cache when available, otherwise via a direct API call). If the username is not found, an error is returned with a list of available users.
|
|
16
|
+
- `add_relationship`: new optional `type_name` parameter accepts string names as alternative to the numeric `type_id` (e.g. `"related_to"`, `"duplicate_of"`, `"depends_on"`, `"blocks"`). Dash variants (`"related-to"`) are also accepted. `type_id` becomes optional — at least one of the two must be provided; `type_id` takes precedence when both are given.
|
|
17
|
+
- `search_issues`: new optional `select` parameter. When provided, each matching issue is fetched from MantisBT and the response is enriched with the requested fields (comma-separated, e.g. `"id,summary,status,handler,priority"`). Without `select` the behaviour is unchanged — only `id` and `score` are returned. `id` and `score` are always included regardless of the `select` value. If an individual issue fetch fails, that result falls back silently to `{id, score}`.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
10
21
|
## [1.4.0] – 2026-03-17
|
|
11
22
|
|
|
12
23
|
### Added
|
package/README.de.md
CHANGED
|
@@ -93,7 +93,7 @@ Falls keine Umgebungsvariablen gesetzt sind, wird `~/.claude/mantis.json` ausgel
|
|
|
93
93
|
|---|---|
|
|
94
94
|
| `get_issue` | Ein Issue anhand seiner ID abrufen |
|
|
95
95
|
| `list_issues` | Issues nach Projekt, Status, Autor u.v.m. filtern; optionales `select` für Feldprojektion und `status` für clientseitige Statusfilterung |
|
|
96
|
-
| `create_issue` | Neues Issue anlegen |
|
|
96
|
+
| `create_issue` | Neues Issue anlegen; optionaler `handler`-Parameter akzeptiert einen Benutzernamen als Alternative zu `handler_id` (wird gegen die Projektmitglieder aufgelöst) |
|
|
97
97
|
| `update_issue` | Bestehendes Issue bearbeiten |
|
|
98
98
|
| `delete_issue` | Issue löschen |
|
|
99
99
|
|
|
@@ -116,7 +116,7 @@ Falls keine Umgebungsvariablen gesetzt sind, wird `~/.claude/mantis.json` ausgel
|
|
|
116
116
|
|
|
117
117
|
| Tool | Beschreibung |
|
|
118
118
|
|---|---|
|
|
119
|
-
| `add_relationship` | Beziehung zwischen zwei Issues erstellen |
|
|
119
|
+
| `add_relationship` | Beziehung zwischen zwei Issues erstellen; optionaler `type_name`-Parameter akzeptiert einen String-Namen (z.B. `"related_to"`, `"duplicate_of"`) als Alternative zur numerischen `type_id` |
|
|
120
120
|
| `remove_relationship` | Beziehung von einem Issue entfernen (die `id` aus dem Beziehungsobjekt verwenden, nicht die type-ID) |
|
|
121
121
|
|
|
122
122
|
### Beobachter
|
|
@@ -157,7 +157,7 @@ Aktivierung mit `MANTIS_SEARCH_ENABLED=true`.
|
|
|
157
157
|
|
|
158
158
|
| Tool | Beschreibung |
|
|
159
159
|
|---|---|
|
|
160
|
-
| `search_issues` | Natürlichsprachige Suche über alle indizierten Issues – liefert Top-N-Ergebnisse mit Cosine-Similarity-Score |
|
|
160
|
+
| `search_issues` | Natürlichsprachige Suche über alle indizierten Issues – liefert Top-N-Ergebnisse mit Cosine-Similarity-Score; optionales `select` (kommagetrennte Feldnamen) reichert jedes Ergebnis mit den angeforderten Issue-Feldern an |
|
|
161
161
|
| `rebuild_search_index` | Suchindex aufbauen oder aktualisieren; `full: true` löscht und baut ihn vollständig neu |
|
|
162
162
|
| `get_search_index_status` | Aktuellen Füllstand des Suchindex zurückgeben: wie viele Issues bereits indiziert sind im Verhältnis zur Gesamtanzahl, plus Zeitstempel der letzten Synchronisation |
|
|
163
163
|
|
|
@@ -188,7 +188,7 @@ npm install sqlite-vec better-sqlite3
|
|
|
188
188
|
| `get_current_user` | Eigenes Benutzerprofil abrufen |
|
|
189
189
|
| `list_languages` | Verfügbare Sprachen auflisten |
|
|
190
190
|
| `get_config` | Server-Konfiguration (Basis-URL, Cache-TTL) anzeigen |
|
|
191
|
-
| `get_issue_enums` | Gültige ID/Name-Paare für alle Enum-Felder zurückgeben (Severity, Status, Priority, Resolution, Reproducibility) — vor `create_issue` / `update_issue` verwenden, um korrekte Werte nachzuschlagen |
|
|
191
|
+
| `get_issue_enums` | Gültige ID/Name-Paare für alle Enum-Felder zurückgeben (Severity, Status, Priority, Resolution, Reproducibility) — vor `create_issue` / `update_issue` verwenden, um korrekte Werte nachzuschlagen; auf lokalisierten Installationen kann jeder Eintrag ein `canonical_name`-Feld mit dem englischen Standard-API-Namen enthalten |
|
|
192
192
|
| `get_mantis_version` | MantisBT-Version abrufen und auf Updates prüfen |
|
|
193
193
|
| `get_mcp_version` | Version dieser mantisbt-mcp-server-Instanz zurückgeben |
|
|
194
194
|
|
package/README.md
CHANGED
|
@@ -93,7 +93,7 @@ If no environment variables are set, `~/.claude/mantis.json` is read:
|
|
|
93
93
|
|---|---|
|
|
94
94
|
| `get_issue` | Retrieve an issue by its numeric ID |
|
|
95
95
|
| `list_issues` | Filter issues by project, status, author, and more; optional `select` for field projection and `status` for client-side status filtering |
|
|
96
|
-
| `create_issue` | Create a new issue |
|
|
96
|
+
| `create_issue` | Create a new issue; optional `handler` parameter accepts a username as alternative to `handler_id` (resolved against project members) |
|
|
97
97
|
| `update_issue` | Update an existing issue |
|
|
98
98
|
| `delete_issue` | Delete an issue |
|
|
99
99
|
|
|
@@ -116,7 +116,7 @@ If no environment variables are set, `~/.claude/mantis.json` is read:
|
|
|
116
116
|
|
|
117
117
|
| Tool | Description |
|
|
118
118
|
|---|---|
|
|
119
|
-
| `add_relationship` | Create a relationship between two issues |
|
|
119
|
+
| `add_relationship` | Create a relationship between two issues; optional `type_name` parameter accepts a string name (e.g. `"related_to"`, `"duplicate_of"`) as alternative to numeric `type_id` |
|
|
120
120
|
| `remove_relationship` | Remove a relationship from an issue (use the `id` from the relationship object, not the type) |
|
|
121
121
|
|
|
122
122
|
### Monitors
|
|
@@ -157,7 +157,7 @@ Activate with `MANTIS_SEARCH_ENABLED=true`.
|
|
|
157
157
|
|
|
158
158
|
| Tool | Description |
|
|
159
159
|
|---|---|
|
|
160
|
-
| `search_issues` | Natural language search over all indexed issues — returns top-N results with cosine similarity score |
|
|
160
|
+
| `search_issues` | Natural language search over all indexed issues — returns top-N results with cosine similarity score; optional `select` (comma-separated field names) enriches each result with the requested issue fields |
|
|
161
161
|
| `rebuild_search_index` | Build or update the search index; `full: true` clears and rebuilds from scratch |
|
|
162
162
|
| `get_search_index_status` | Return the current fill level of the search index: how many issues are indexed vs. total, and the timestamp of the last sync |
|
|
163
163
|
|
|
@@ -188,7 +188,7 @@ npm install sqlite-vec better-sqlite3
|
|
|
188
188
|
| `get_current_user` | Retrieve your own user profile |
|
|
189
189
|
| `list_languages` | List available languages |
|
|
190
190
|
| `get_config` | Show server configuration (base URL, cache TTL) |
|
|
191
|
-
| `get_issue_enums` | Return valid ID/name pairs for all issue enum fields (severity, status, priority, resolution, reproducibility) — use before `create_issue` / `update_issue` to look up correct values |
|
|
191
|
+
| `get_issue_enums` | Return valid ID/name pairs for all issue enum fields (severity, status, priority, resolution, reproducibility) — use before `create_issue` / `update_issue` to look up correct values; on localized installations each entry may include a `canonical_name` with the standard English API name |
|
|
192
192
|
| `get_mantis_version` | Get MantisBT version and check for updates |
|
|
193
193
|
| `get_mcp_version` | Return the version of this mantisbt-mcp-server instance |
|
|
194
194
|
|
package/dist/constants.js
CHANGED
|
@@ -10,12 +10,89 @@ export const RELATIONSHIP_TYPES = {
|
|
|
10
10
|
HAS_DUPLICATE: 4,
|
|
11
11
|
};
|
|
12
12
|
// ---------------------------------------------------------------------------
|
|
13
|
+
// Relationship type name → ID mapping (string aliases accepted by add_relationship)
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
export const RELATIONSHIP_NAME_TO_ID = {
|
|
16
|
+
'duplicate_of': RELATIONSHIP_TYPES.DUPLICATE_OF,
|
|
17
|
+
'duplicate-of': RELATIONSHIP_TYPES.DUPLICATE_OF,
|
|
18
|
+
'duplicateof': RELATIONSHIP_TYPES.DUPLICATE_OF,
|
|
19
|
+
'related_to': RELATIONSHIP_TYPES.RELATED_TO,
|
|
20
|
+
'related-to': RELATIONSHIP_TYPES.RELATED_TO,
|
|
21
|
+
'relatedto': RELATIONSHIP_TYPES.RELATED_TO,
|
|
22
|
+
'parent_of': RELATIONSHIP_TYPES.PARENT_OF,
|
|
23
|
+
'parent-of': RELATIONSHIP_TYPES.PARENT_OF,
|
|
24
|
+
'parentof': RELATIONSHIP_TYPES.PARENT_OF,
|
|
25
|
+
'depends_on': RELATIONSHIP_TYPES.PARENT_OF,
|
|
26
|
+
'depends-on': RELATIONSHIP_TYPES.PARENT_OF,
|
|
27
|
+
'dependson': RELATIONSHIP_TYPES.PARENT_OF,
|
|
28
|
+
'child_of': RELATIONSHIP_TYPES.CHILD_OF,
|
|
29
|
+
'child-of': RELATIONSHIP_TYPES.CHILD_OF,
|
|
30
|
+
'childof': RELATIONSHIP_TYPES.CHILD_OF,
|
|
31
|
+
'blocks': RELATIONSHIP_TYPES.CHILD_OF,
|
|
32
|
+
'has_duplicate': RELATIONSHIP_TYPES.HAS_DUPLICATE,
|
|
33
|
+
'has-duplicate': RELATIONSHIP_TYPES.HAS_DUPLICATE,
|
|
34
|
+
'hasduplicate': RELATIONSHIP_TYPES.HAS_DUPLICATE,
|
|
35
|
+
};
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
13
37
|
// Status names (internal English names used in API calls)
|
|
14
38
|
// ---------------------------------------------------------------------------
|
|
15
39
|
// MantisBT default status ID for "resolved". Issues with status.id strictly
|
|
16
40
|
// below this value are considered open (new/feedback/acknowledged/confirmed/assigned).
|
|
17
41
|
export const MANTIS_RESOLVED_STATUS_ID = 80;
|
|
18
42
|
// ---------------------------------------------------------------------------
|
|
43
|
+
// Canonical English enum names for standard MantisBT installations.
|
|
44
|
+
// Keyed by the enum group name (without _enum_string suffix).
|
|
45
|
+
// Used by get_issue_enums to add a canonical_name field on localized installs.
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
export const MANTIS_CANONICAL_ENUM_NAMES = {
|
|
48
|
+
severity: {
|
|
49
|
+
10: 'feature',
|
|
50
|
+
20: 'trivial',
|
|
51
|
+
30: 'text',
|
|
52
|
+
40: 'tweak',
|
|
53
|
+
50: 'minor',
|
|
54
|
+
60: 'major',
|
|
55
|
+
70: 'crash',
|
|
56
|
+
80: 'block',
|
|
57
|
+
},
|
|
58
|
+
status: {
|
|
59
|
+
10: 'new',
|
|
60
|
+
20: 'feedback',
|
|
61
|
+
30: 'acknowledged',
|
|
62
|
+
40: 'confirmed',
|
|
63
|
+
50: 'assigned',
|
|
64
|
+
80: 'resolved',
|
|
65
|
+
90: 'closed',
|
|
66
|
+
},
|
|
67
|
+
priority: {
|
|
68
|
+
10: 'none',
|
|
69
|
+
20: 'low',
|
|
70
|
+
30: 'normal',
|
|
71
|
+
40: 'high',
|
|
72
|
+
50: 'urgent',
|
|
73
|
+
60: 'immediate',
|
|
74
|
+
},
|
|
75
|
+
resolution: {
|
|
76
|
+
10: 'open',
|
|
77
|
+
20: 'fixed',
|
|
78
|
+
30: 'reopened',
|
|
79
|
+
40: 'unable to duplicate',
|
|
80
|
+
50: 'not fixable',
|
|
81
|
+
60: 'duplicate',
|
|
82
|
+
70: 'no change required',
|
|
83
|
+
80: 'suspended',
|
|
84
|
+
90: 'wont fix',
|
|
85
|
+
},
|
|
86
|
+
reproducibility: {
|
|
87
|
+
10: 'always',
|
|
88
|
+
30: 'sometimes',
|
|
89
|
+
50: 'random',
|
|
90
|
+
70: 'have not tried',
|
|
91
|
+
90: 'unable to reproduce',
|
|
92
|
+
100: 'N/A',
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
19
96
|
// Issue enum config option names
|
|
20
97
|
// ---------------------------------------------------------------------------
|
|
21
98
|
export const ISSUE_ENUM_OPTIONS = [
|
package/dist/index.js
CHANGED
|
@@ -42,7 +42,7 @@ async function createMcpServer() {
|
|
|
42
42
|
name: 'mantisbt-mcp-server',
|
|
43
43
|
version,
|
|
44
44
|
});
|
|
45
|
-
registerIssueTools(server, client);
|
|
45
|
+
registerIssueTools(server, client, cache);
|
|
46
46
|
registerNoteTools(server, client);
|
|
47
47
|
registerFileTools(server, client);
|
|
48
48
|
registerRelationshipTools(server, client);
|
package/dist/search/tools.js
CHANGED
|
@@ -27,13 +27,16 @@ export function registerSearchTools(server, client, store, embedder) {
|
|
|
27
27
|
.max(50)
|
|
28
28
|
.default(10)
|
|
29
29
|
.describe('Number of results to return (default: 10, max: 50)'),
|
|
30
|
+
select: z.string().optional().describe('Comma-separated list of fields to include for each result (e.g. "id,summary,status,handler,priority"). ' +
|
|
31
|
+
'When provided, each matching issue is fetched from MantisBT and enriched with the requested fields. ' +
|
|
32
|
+
'The relevance score is always included. Without this parameter only id and score are returned.'),
|
|
30
33
|
}),
|
|
31
34
|
annotations: {
|
|
32
35
|
readOnlyHint: true,
|
|
33
36
|
destructiveHint: false,
|
|
34
37
|
idempotentHint: true,
|
|
35
38
|
},
|
|
36
|
-
}, async ({ query, top_n }) => {
|
|
39
|
+
}, async ({ query, top_n, select }) => {
|
|
37
40
|
try {
|
|
38
41
|
const count = await store.count();
|
|
39
42
|
if (count === 0) {
|
|
@@ -49,8 +52,30 @@ export function registerSearchTools(server, client, store, embedder) {
|
|
|
49
52
|
}
|
|
50
53
|
const queryVector = await embedder.embed(query);
|
|
51
54
|
const results = await store.search(queryVector, top_n);
|
|
55
|
+
if (!select) {
|
|
56
|
+
return {
|
|
57
|
+
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
const fields = select.split(',').map(f => f.trim()).filter(Boolean);
|
|
61
|
+
const enriched = await Promise.all(results.map(async ({ id, score }) => {
|
|
62
|
+
try {
|
|
63
|
+
const issueResult = await client.get(`issues/${id}`);
|
|
64
|
+
const issue = issueResult.issues?.[0] ?? {};
|
|
65
|
+
const projected = { id, score };
|
|
66
|
+
for (const field of fields) {
|
|
67
|
+
if (field !== 'id' && field in issue) {
|
|
68
|
+
projected[field] = issue[field];
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return projected;
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return { id, score };
|
|
75
|
+
}
|
|
76
|
+
}));
|
|
52
77
|
return {
|
|
53
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
78
|
+
content: [{ type: 'text', text: JSON.stringify(enriched, null, 2) }],
|
|
54
79
|
};
|
|
55
80
|
}
|
|
56
81
|
catch (error) {
|
package/dist/tools/config.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { ISSUE_ENUM_OPTIONS } from '../constants.js';
|
|
2
|
+
import { ISSUE_ENUM_OPTIONS, MANTIS_CANONICAL_ENUM_NAMES } from '../constants.js';
|
|
3
3
|
import { getVersionHint } from '../version-hint.js';
|
|
4
4
|
function errorText(msg) {
|
|
5
5
|
const vh = getVersionHint();
|
|
@@ -21,6 +21,10 @@ function parseEnumString(raw) {
|
|
|
21
21
|
})
|
|
22
22
|
.filter((e) => e !== null);
|
|
23
23
|
}
|
|
24
|
+
function resolveCanonicalName(id, name, canonicalMap) {
|
|
25
|
+
const canonical = canonicalMap[id];
|
|
26
|
+
return canonical !== undefined && canonical !== name ? canonical : undefined;
|
|
27
|
+
}
|
|
24
28
|
export function registerConfigTools(server, client, cache) {
|
|
25
29
|
// ---------------------------------------------------------------------------
|
|
26
30
|
// get_config
|
|
@@ -130,14 +134,24 @@ to use for API calls — regardless of language.`,
|
|
|
130
134
|
const key = keyMap[option];
|
|
131
135
|
if (!key)
|
|
132
136
|
continue;
|
|
137
|
+
const canonicalMap = MANTIS_CANONICAL_ENUM_NAMES[key] ?? {};
|
|
133
138
|
if (typeof value === 'string') {
|
|
134
|
-
enums[key] = parseEnumString(value)
|
|
139
|
+
enums[key] = parseEnumString(value).map(({ id, name }) => {
|
|
140
|
+
const entry = { id, name };
|
|
141
|
+
const canonical_name = resolveCanonicalName(id, name, canonicalMap);
|
|
142
|
+
if (canonical_name !== undefined)
|
|
143
|
+
entry.canonical_name = canonical_name;
|
|
144
|
+
return entry;
|
|
145
|
+
});
|
|
135
146
|
}
|
|
136
147
|
else if (Array.isArray(value)) {
|
|
137
148
|
enums[key] = value.map(({ id, name, label }) => {
|
|
138
149
|
const entry = { id, name };
|
|
139
150
|
if (label && label !== name)
|
|
140
151
|
entry.label = label;
|
|
152
|
+
const canonical_name = resolveCanonicalName(id, name, canonicalMap);
|
|
153
|
+
if (canonical_name !== undefined)
|
|
154
|
+
entry.canonical_name = canonical_name;
|
|
141
155
|
return entry;
|
|
142
156
|
});
|
|
143
157
|
}
|
package/dist/tools/issues.js
CHANGED
|
@@ -7,7 +7,7 @@ function errorText(msg) {
|
|
|
7
7
|
const hint = vh?.getUpdateHint();
|
|
8
8
|
return hint ? `Error: ${msg}\n\n${hint}` : `Error: ${msg}`;
|
|
9
9
|
}
|
|
10
|
-
export function registerIssueTools(server, client) {
|
|
10
|
+
export function registerIssueTools(server, client, cache) {
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
12
|
// get_issue
|
|
13
13
|
// ---------------------------------------------------------------------------
|
|
@@ -141,13 +141,38 @@ export function registerIssueTools(server, client) {
|
|
|
141
141
|
priority: z.string().optional().describe('Priority name (e.g. "normal", "high", "urgent", "immediate", "low", "none")'),
|
|
142
142
|
severity: z.string().default('minor').describe('Severity name (e.g. "minor", "major", "crash", "block", "feature", "trivial", "text") — default: "minor"'),
|
|
143
143
|
handler_id: z.coerce.number().int().positive().optional().describe('User ID of the person to assign the issue to'),
|
|
144
|
+
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.'),
|
|
144
145
|
}),
|
|
145
146
|
annotations: {
|
|
146
147
|
readOnlyHint: false,
|
|
147
148
|
destructiveHint: false,
|
|
148
149
|
idempotentHint: false,
|
|
149
150
|
},
|
|
150
|
-
}, async ({ summary, description, project_id, category, priority, severity, handler_id }) => {
|
|
151
|
+
}, async ({ summary, description, project_id, category, priority, severity, handler_id, handler }) => {
|
|
152
|
+
// Resolve handler username to handler_id when only a name is given
|
|
153
|
+
let resolvedHandlerId = handler_id;
|
|
154
|
+
if (resolvedHandlerId === undefined && handler !== undefined) {
|
|
155
|
+
const metadata = await cache.loadIfValid();
|
|
156
|
+
let users = metadata?.byProject[project_id]?.users ?? [];
|
|
157
|
+
if (users.length === 0) {
|
|
158
|
+
try {
|
|
159
|
+
const usersResult = await client.get(`projects/${project_id}/users`);
|
|
160
|
+
users = usersResult.users ?? [];
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
users = [];
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const user = users.find(u => u.name === handler || u.real_name === handler);
|
|
167
|
+
if (!user) {
|
|
168
|
+
const names = users.map(u => u.name).join(', ');
|
|
169
|
+
return {
|
|
170
|
+
content: [{ type: 'text', text: errorText(`User "${handler}" not found in project ${project_id}. Available users: ${names || 'none (run sync_metadata or check project_id)'}`) }],
|
|
171
|
+
isError: true,
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
resolvedHandlerId = user.id;
|
|
175
|
+
}
|
|
151
176
|
try {
|
|
152
177
|
const body = {
|
|
153
178
|
summary,
|
|
@@ -158,11 +183,26 @@ export function registerIssueTools(server, client) {
|
|
|
158
183
|
if (priority)
|
|
159
184
|
body.priority = { name: priority };
|
|
160
185
|
body.severity = { name: severity };
|
|
161
|
-
if (
|
|
162
|
-
body.handler = { id:
|
|
163
|
-
const
|
|
186
|
+
if (resolvedHandlerId)
|
|
187
|
+
body.handler = { id: resolvedHandlerId };
|
|
188
|
+
const raw = await client.post('issues', body);
|
|
189
|
+
const partial = ('issue' in raw && typeof raw['issue'] === 'object' && raw['issue'] !== null)
|
|
190
|
+
? raw['issue']
|
|
191
|
+
: raw;
|
|
192
|
+
let issue = partial;
|
|
193
|
+
if (!('summary' in partial)) {
|
|
194
|
+
// Older MantisBT returned only { id: N } — fetch the full issue.
|
|
195
|
+
// Suppress GET errors: the issue was already created.
|
|
196
|
+
try {
|
|
197
|
+
const fetched = await client.get(`issues/${partial.id}`);
|
|
198
|
+
issue = fetched.issues?.[0] ?? partial;
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
// unable to fetch details — return minimal object
|
|
202
|
+
}
|
|
203
|
+
}
|
|
164
204
|
return {
|
|
165
|
-
content: [{ type: 'text', text: JSON.stringify(
|
|
205
|
+
content: [{ type: 'text', text: JSON.stringify(issue, null, 2) }],
|
|
166
206
|
};
|
|
167
207
|
}
|
|
168
208
|
catch (error) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { RELATIONSHIP_TYPES } from '../constants.js';
|
|
2
|
+
import { RELATIONSHIP_TYPES, RELATIONSHIP_NAME_TO_ID } from '../constants.js';
|
|
3
3
|
import { getVersionHint } from '../version-hint.js';
|
|
4
4
|
function errorText(msg) {
|
|
5
5
|
const vh = getVersionHint();
|
|
@@ -15,31 +15,47 @@ export function registerRelationshipTools(server, client) {
|
|
|
15
15
|
title: 'Add Issue Relationship',
|
|
16
16
|
description: `Add a relationship between two MantisBT issues.
|
|
17
17
|
|
|
18
|
-
Relationship
|
|
19
|
-
- ${RELATIONSHIP_TYPES.DUPLICATE_OF}
|
|
20
|
-
- ${RELATIONSHIP_TYPES.RELATED_TO}
|
|
21
|
-
- ${RELATIONSHIP_TYPES.PARENT_OF}
|
|
22
|
-
- ${RELATIONSHIP_TYPES.CHILD_OF}
|
|
23
|
-
- ${RELATIONSHIP_TYPES.HAS_DUPLICATE}
|
|
18
|
+
Relationship types — use either type_id (numeric) or type_name (string):
|
|
19
|
+
- ${RELATIONSHIP_TYPES.DUPLICATE_OF} / "duplicate_of" — this issue is a duplicate of target
|
|
20
|
+
- ${RELATIONSHIP_TYPES.RELATED_TO} / "related_to" — this issue is related to target
|
|
21
|
+
- ${RELATIONSHIP_TYPES.PARENT_OF} / "parent_of" — this issue depends on target (target must be done first); alias: "depends_on"
|
|
22
|
+
- ${RELATIONSHIP_TYPES.CHILD_OF} / "child_of" — this issue blocks target (target can't proceed until this is done); alias: "blocks"
|
|
23
|
+
- ${RELATIONSHIP_TYPES.HAS_DUPLICATE} / "has_duplicate" — this issue has target as a duplicate
|
|
24
24
|
|
|
25
25
|
Directionality note: "A child_of B" means A blocks B. "A parent_of B" means A depends on B.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
Dash variants (e.g. "related-to") are also accepted for type_name.`,
|
|
28
28
|
inputSchema: z.object({
|
|
29
29
|
issue_id: z.coerce.number().int().positive().describe('The source issue ID (the one the relationship is added to)'),
|
|
30
30
|
target_id: z.coerce.number().int().positive().describe('The target issue ID'),
|
|
31
|
-
type_id: z.coerce.number().int().min(0).max(4).describe(
|
|
31
|
+
type_id: z.coerce.number().int().min(0).max(4).optional().describe('Relationship type ID: 0=duplicate_of, 1=related_to, 2=parent_of (depends on), 3=child_of (blocks), 4=has_duplicate. Use either type_id or type_name.'),
|
|
32
|
+
type_name: z.string().optional().describe('Relationship type name as alternative to type_id. Accepted: "duplicate_of", "related_to", "parent_of" (or "depends_on"), "child_of" (or "blocks"), "has_duplicate". Dash variants (e.g. "related-to") also work.'),
|
|
32
33
|
}),
|
|
33
34
|
annotations: {
|
|
34
35
|
readOnlyHint: false,
|
|
35
36
|
destructiveHint: false,
|
|
36
37
|
idempotentHint: false,
|
|
37
38
|
},
|
|
38
|
-
}, async ({ issue_id, target_id, type_id }) => {
|
|
39
|
+
}, async ({ issue_id, target_id, type_id, type_name }) => {
|
|
40
|
+
// Resolve type_id from type_name when type_id is not provided
|
|
41
|
+
let resolvedTypeId = type_id;
|
|
42
|
+
if (resolvedTypeId === undefined) {
|
|
43
|
+
if (type_name === undefined) {
|
|
44
|
+
return { content: [{ type: 'text', text: errorText('Either type_id or type_name must be provided') }], isError: true };
|
|
45
|
+
}
|
|
46
|
+
const normalized = type_name.toLowerCase().replace(/-/g, '_');
|
|
47
|
+
resolvedTypeId = RELATIONSHIP_NAME_TO_ID[normalized];
|
|
48
|
+
if (resolvedTypeId === undefined) {
|
|
49
|
+
return {
|
|
50
|
+
content: [{ type: 'text', text: errorText(`Unknown relationship type name: "${type_name}". Valid values: duplicate_of, related_to, parent_of, child_of, has_duplicate`) }],
|
|
51
|
+
isError: true,
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
39
55
|
try {
|
|
40
56
|
const body = {
|
|
41
57
|
issue: { id: target_id },
|
|
42
|
-
type: { id:
|
|
58
|
+
type: { id: resolvedTypeId },
|
|
43
59
|
};
|
|
44
60
|
const result = await client.post(`issues/${issue_id}/relationships`, body);
|
|
45
61
|
return {
|
package/package.json
CHANGED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 51,
|
|
3
|
+
"name": "domAgent",
|
|
4
|
+
"real_name": "Dominik Pesch (via Agent)",
|
|
5
|
+
"email": "d.pesch+agent@11com7.de",
|
|
6
|
+
"language": "german",
|
|
7
|
+
"timezone": "Europe/Berlin",
|
|
8
|
+
"access_level": {
|
|
9
|
+
"id": 70,
|
|
10
|
+
"name": "manager",
|
|
11
|
+
"label": "Manager"
|
|
12
|
+
},
|
|
13
|
+
"created_at": "2026-02-28T09:08:30+01:00",
|
|
14
|
+
"projects": [
|
|
15
|
+
{
|
|
16
|
+
"id": 30,
|
|
17
|
+
"name": "((Haus)) (Bolliggasse 1a)"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": 54,
|
|
21
|
+
"name": "11com7 Claude MetaRepo"
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
"id": 2,
|
|
25
|
+
"name": "11com7 ContentGo-Lib"
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"id": 48,
|
|
29
|
+
"name": "11com7 DevServer"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"id": 25,
|
|
33
|
+
"name": "11com7 GmbH"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": 39,
|
|
37
|
+
"name": "11com7 Hosting (Keyweb, Ansible)"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"id": 44,
|
|
41
|
+
"name": "11com7-Ausbildung-FIAE"
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"id": 52,
|
|
45
|
+
"name": "11com7-DVMS (Datenschutzverletzungsmelder)"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": 5,
|
|
49
|
+
"name": "11com7-Homepage"
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
"id": 27,
|
|
53
|
+
"name": "b11com7"
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
"id": 3,
|
|
57
|
+
"name": "BIBB NAA309"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"id": 11,
|
|
61
|
+
"name": "BIBB wbmonitor"
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
"id": 38,
|
|
65
|
+
"name": "DSGV Budgetanalyse 3"
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"id": 28,
|
|
69
|
+
"name": "DSGV Finanzchecker (App)"
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
"id": 45,
|
|
73
|
+
"name": "DSGV Finanzchecker (Landingpage)"
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
"id": 40,
|
|
77
|
+
"name": "DSGV Login"
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"id": 26,
|
|
81
|
+
"name": "DSGV Referenzbudgets"
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": 8,
|
|
85
|
+
"name": "DSGV Vortragsservice"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"id": 14,
|
|
89
|
+
"name": "DSGV Web-Budgetplaner"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"id": 42,
|
|
93
|
+
"name": "Lingua-World"
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
"id": 49,
|
|
97
|
+
"name": "SIK M&E-App"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"id": 21,
|
|
101
|
+
"name": "Uni Bonn (Hausprint V2)"
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": 53,
|
|
105
|
+
"name": "ZEEEM GSM-Landingpage"
|
|
106
|
+
}
|
|
107
|
+
]
|
|
108
|
+
}
|