@dpesch/mantisbt-mcp-server 1.6.1 → 1.7.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 +18 -0
- package/dist/tools/issues.js +32 -9
- package/package.json +2 -2
- package/scripts/hooks/pre-push.mjs +10 -2
- package/server.json +2 -2
- package/tests/tools/issues.test.ts +202 -8
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,24 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [1.7.0] – 2026-03-26
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `create_issue`: new optional parameters `version`, `target_version`, `fixed_in_version`, `steps_to_reproduce`, `additional_information`, `reproducibility`, and `view_state` — these fields were already supported by the MantisBT REST API on issue creation but were missing from the tool, requiring a second `update_issue` call to set them.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## [1.6.2] – 2026-03-24
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- `create_issue`: default priority is now `"normal"`, matching the MantisBT UI default. Previously no priority was sent when omitted, causing MantisBT to fall back to its server-side default of `"low"`.
|
|
21
|
+
- `create_issue`: `description` is now a required field (minimum 1 character). Previously it defaulted to an empty string, which caused the MantisBT API to reject the request. The validation error is now surfaced immediately by the MCP server before the API is called.
|
|
22
|
+
|
|
23
|
+
### Changed
|
|
24
|
+
- npm keywords extended: added `issue-tracker`, `bug-tracker`, `claude`, `claude-code` for better discoverability.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
10
28
|
## [1.6.1] – 2026-03-24
|
|
11
29
|
|
|
12
30
|
### Changed
|
package/dist/tools/issues.js
CHANGED
|
@@ -144,20 +144,27 @@ export function registerIssueTools(server, client, cache) {
|
|
|
144
144
|
description: 'Create a new MantisBT issue. Returns the created issue including its assigned ID.',
|
|
145
145
|
inputSchema: z.object({
|
|
146
146
|
summary: z.string().min(1).describe('Issue summary/title'),
|
|
147
|
-
description: z.string().
|
|
147
|
+
description: z.string().min(1).describe('Detailed issue description. Required — do not create issues without a description. Plain text or Markdown.'),
|
|
148
148
|
project_id: z.coerce.number().int().positive().describe('Project ID the issue belongs to'),
|
|
149
149
|
category: z.string().min(1).describe('Category name (use get_project_categories to list available categories)'),
|
|
150
|
-
priority: z.string().
|
|
150
|
+
priority: z.string().default('normal').describe('Priority name — must be a canonical English name: none, low, normal, high, urgent, immediate. Default: "normal". Call get_issue_enums to see localized labels.'),
|
|
151
151
|
severity: z.string().default('minor').describe('Severity name — must be a canonical English name: feature, trivial, text, tweak, minor, major, crash, block. Default: "minor". Call get_issue_enums to see localized labels.'),
|
|
152
152
|
handler_id: z.coerce.number().int().positive().optional().describe('User ID of the person to assign the issue to'),
|
|
153
153
|
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
|
+
version: z.string().optional().describe('Affected product version name (use get_project_versions to list available versions)'),
|
|
155
|
+
target_version: z.string().optional().describe('Target version name — version in which the issue is planned to be fixed (use get_project_versions to list available versions)'),
|
|
156
|
+
fixed_in_version: z.string().optional().describe('Version name in which the issue was fixed (use get_project_versions to list available versions)'),
|
|
157
|
+
steps_to_reproduce: z.string().optional().describe('Steps to reproduce the issue. Plain text or Markdown.'),
|
|
158
|
+
additional_information: z.string().optional().describe('Additional information about the issue. Plain text or Markdown.'),
|
|
159
|
+
reproducibility: z.string().optional().describe('Reproducibility — must be a canonical English name: always, sometimes, random, have not tried, unable to reproduce, N/A. Call get_issue_enums to see localized labels.'),
|
|
160
|
+
view_state: z.enum(['public', 'private']).optional().describe('Visibility of the issue: "public" (default) or "private"'),
|
|
154
161
|
}),
|
|
155
162
|
annotations: {
|
|
156
163
|
readOnlyHint: false,
|
|
157
164
|
destructiveHint: false,
|
|
158
165
|
idempotentHint: false,
|
|
159
166
|
},
|
|
160
|
-
}, async ({ summary, description, project_id, category, priority, severity, handler_id, handler }) => {
|
|
167
|
+
}, async ({ summary, description, project_id, category, priority, severity, handler_id, handler, version, target_version, fixed_in_version, steps_to_reproduce, additional_information, reproducibility, view_state }) => {
|
|
161
168
|
// Resolve handler username to handler_id when only a name is given
|
|
162
169
|
let resolvedHandlerId = handler_id;
|
|
163
170
|
if (resolvedHandlerId === undefined && handler !== undefined) {
|
|
@@ -189,18 +196,34 @@ export function registerIssueTools(server, client, cache) {
|
|
|
189
196
|
project: { id: project_id },
|
|
190
197
|
category: { name: category },
|
|
191
198
|
};
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
body.priority = priorityResolved;
|
|
197
|
-
}
|
|
199
|
+
const priorityResolved = resolveEnum('priority', priority);
|
|
200
|
+
if (typeof priorityResolved === 'string')
|
|
201
|
+
return { content: [{ type: 'text', text: errorText(priorityResolved) }], isError: true };
|
|
202
|
+
body.priority = priorityResolved;
|
|
198
203
|
const severityResolved = resolveEnum('severity', severity);
|
|
199
204
|
if (typeof severityResolved === 'string')
|
|
200
205
|
return { content: [{ type: 'text', text: errorText(severityResolved) }], isError: true };
|
|
201
206
|
body.severity = severityResolved;
|
|
202
207
|
if (resolvedHandlerId)
|
|
203
208
|
body.handler = { id: resolvedHandlerId };
|
|
209
|
+
if (version !== undefined)
|
|
210
|
+
body.version = { name: version };
|
|
211
|
+
if (target_version !== undefined)
|
|
212
|
+
body.target_version = { name: target_version };
|
|
213
|
+
if (fixed_in_version !== undefined)
|
|
214
|
+
body.fixed_in_version = { name: fixed_in_version };
|
|
215
|
+
if (steps_to_reproduce !== undefined)
|
|
216
|
+
body.steps_to_reproduce = steps_to_reproduce;
|
|
217
|
+
if (additional_information !== undefined)
|
|
218
|
+
body.additional_information = additional_information;
|
|
219
|
+
if (reproducibility !== undefined) {
|
|
220
|
+
const reproducibilityResolved = resolveEnum('reproducibility', reproducibility);
|
|
221
|
+
if (typeof reproducibilityResolved === 'string')
|
|
222
|
+
return { content: [{ type: 'text', text: errorText(reproducibilityResolved) }], isError: true };
|
|
223
|
+
body.reproducibility = reproducibilityResolved;
|
|
224
|
+
}
|
|
225
|
+
if (view_state !== undefined)
|
|
226
|
+
body.view_state = { name: view_state };
|
|
204
227
|
const raw = await client.post('issues', body);
|
|
205
228
|
const partial = ('issue' in raw && typeof raw['issue'] === 'object' && raw['issue'] !== null)
|
|
206
229
|
? raw['issue']
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dpesch/mantisbt-mcp-server",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
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",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"url": "https://github.com/dpesch/mantisbt-mcp-server",
|
|
15
15
|
"_note": "GitHub mirror for ecosystem compatibility only — canonical source: https://codeberg.org/dpesch/mantisbt-mcp-server"
|
|
16
16
|
},
|
|
17
|
-
"keywords": ["mcp", "mcp-server", "mantisbt", "bugtracker", "mantis", "model-context-protocol"],
|
|
17
|
+
"keywords": ["mcp", "mcp-server", "mantisbt", "bugtracker", "mantis", "issue-tracker", "bug-tracker", "model-context-protocol", "claude", "claude-code"],
|
|
18
18
|
"main": "dist/index.js",
|
|
19
19
|
"bin": {
|
|
20
20
|
"mantisbt-mcp-server": "dist/index.js"
|
|
@@ -166,9 +166,17 @@ rl.on('close', () => {
|
|
|
166
166
|
shaMap[sha] = makeFilteredCommit(sha, filteredTree, mappedParents);
|
|
167
167
|
}
|
|
168
168
|
} else {
|
|
169
|
-
// Fallback: anchor not found
|
|
169
|
+
// Fallback: anchor not found.
|
|
170
|
+
// If the remote SHA doesn't exist locally (filtered commit from a prior push),
|
|
171
|
+
// tags can be safely skipped (immutable — already correct on Codeberg).
|
|
172
|
+
// Branches fall back to orphan filtering (best effort).
|
|
173
|
+
const remoteExists = !!gitOptional(`rev-parse --verify "${actualRemoteSha}"`);
|
|
174
|
+
if (!remoteExists && remoteRef.startsWith('refs/tags/')) {
|
|
175
|
+
console.log(` ↷ ${label} already on Codeberg — skipping`);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
170
178
|
console.warn(` ⚠ Could not find local base for ${label}, filtering tip only`);
|
|
171
|
-
shaMap[localSha] = makeFilteredCommit(localSha, filterTree(localSha), [actualRemoteSha]);
|
|
179
|
+
shaMap[localSha] = makeFilteredCommit(localSha, filterTree(localSha), remoteExists ? [actualRemoteSha] : []);
|
|
172
180
|
}
|
|
173
181
|
}
|
|
174
182
|
|
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.7.0",
|
|
7
7
|
"packages": [
|
|
8
8
|
{
|
|
9
9
|
"registryType": "npm",
|
|
10
10
|
"identifier": "@dpesch/mantisbt-mcp-server",
|
|
11
|
-
"version": "1.
|
|
11
|
+
"version": "1.7.0",
|
|
12
12
|
"runtimeHint": "npx",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
@@ -123,6 +123,7 @@ describe('create_issue', () => {
|
|
|
123
123
|
|
|
124
124
|
await mockServer.callTool('create_issue', {
|
|
125
125
|
summary: 'Test issue',
|
|
126
|
+
description: 'Test description.',
|
|
126
127
|
project_id: 1,
|
|
127
128
|
category: 'General',
|
|
128
129
|
}, { validate: true });
|
|
@@ -138,7 +139,7 @@ describe('create_issue', () => {
|
|
|
138
139
|
);
|
|
139
140
|
|
|
140
141
|
const result = await mockServer.callTool('create_issue', {
|
|
141
|
-
summary: 'New issue', project_id: 1, category: 'General',
|
|
142
|
+
summary: 'New issue', description: 'New issue description.', project_id: 1, category: 'General',
|
|
142
143
|
}, { validate: true });
|
|
143
144
|
|
|
144
145
|
expect(result.isError).toBeUndefined();
|
|
@@ -156,7 +157,7 @@ describe('create_issue', () => {
|
|
|
156
157
|
.mockResolvedValueOnce(makeResponse(200, JSON.stringify({ issues: [fullIssue] })));
|
|
157
158
|
|
|
158
159
|
const result = await mockServer.callTool('create_issue', {
|
|
159
|
-
summary: 'Created issue', project_id: 1, category: 'General',
|
|
160
|
+
summary: 'Created issue', description: 'Created issue description.', project_id: 1, category: 'General',
|
|
160
161
|
}, { validate: true });
|
|
161
162
|
|
|
162
163
|
expect(result.isError).toBeUndefined();
|
|
@@ -174,7 +175,7 @@ describe('create_issue', () => {
|
|
|
174
175
|
.mockResolvedValueOnce(makeResponse(500, 'Server Error'));
|
|
175
176
|
|
|
176
177
|
const result = await mockServer.callTool('create_issue', {
|
|
177
|
-
summary: 'Test', project_id: 1, category: 'General',
|
|
178
|
+
summary: 'Test', description: 'Test description.', project_id: 1, category: 'General',
|
|
178
179
|
}, { validate: true });
|
|
179
180
|
|
|
180
181
|
expect(result.isError).toBeUndefined();
|
|
@@ -189,6 +190,7 @@ describe('create_issue', () => {
|
|
|
189
190
|
|
|
190
191
|
await mockServer.callTool('create_issue', {
|
|
191
192
|
summary: 'Crash bug',
|
|
193
|
+
description: 'Crash bug description.',
|
|
192
194
|
project_id: 1,
|
|
193
195
|
category: 'General',
|
|
194
196
|
severity: 'crash',
|
|
@@ -198,9 +200,33 @@ describe('create_issue', () => {
|
|
|
198
200
|
expect(body.severity).toEqual({ id: 70 });
|
|
199
201
|
});
|
|
200
202
|
|
|
203
|
+
it('returns a validation error when description is missing', async () => {
|
|
204
|
+
const result = await mockServer.callTool('create_issue', {
|
|
205
|
+
summary: 'Test',
|
|
206
|
+
project_id: 1,
|
|
207
|
+
category: 'General',
|
|
208
|
+
}, { validate: true });
|
|
209
|
+
|
|
210
|
+
expect(result.isError).toBe(true);
|
|
211
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
it('returns a validation error when description is empty', async () => {
|
|
215
|
+
const result = await mockServer.callTool('create_issue', {
|
|
216
|
+
summary: 'Test',
|
|
217
|
+
description: '',
|
|
218
|
+
project_id: 1,
|
|
219
|
+
category: 'General',
|
|
220
|
+
}, { validate: true });
|
|
221
|
+
|
|
222
|
+
expect(result.isError).toBe(true);
|
|
223
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
224
|
+
});
|
|
225
|
+
|
|
201
226
|
it('returns an error for an unknown severity name', async () => {
|
|
202
227
|
const result = await mockServer.callTool('create_issue', {
|
|
203
228
|
summary: 'Test',
|
|
229
|
+
description: 'Test description.',
|
|
204
230
|
project_id: 1,
|
|
205
231
|
category: 'General',
|
|
206
232
|
severity: 'schwerer Fehler',
|
|
@@ -211,6 +237,174 @@ describe('create_issue', () => {
|
|
|
211
237
|
expect(result.content[0]!.text).toContain('minor');
|
|
212
238
|
expect(fetch).not.toHaveBeenCalled();
|
|
213
239
|
});
|
|
240
|
+
|
|
241
|
+
it('creates issue without any optional fields (backward compatibility)', async () => {
|
|
242
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
243
|
+
makeResponse(201, JSON.stringify({ issue: { id: 300, summary: 'Minimal' } }))
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
const result = await mockServer.callTool('create_issue', {
|
|
247
|
+
summary: 'Minimal issue',
|
|
248
|
+
description: 'Minimal description.',
|
|
249
|
+
project_id: 1,
|
|
250
|
+
category: 'General',
|
|
251
|
+
}, { validate: true });
|
|
252
|
+
|
|
253
|
+
expect(result.isError).toBeUndefined();
|
|
254
|
+
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as Record<string, unknown>;
|
|
255
|
+
expect(body.version).toBeUndefined();
|
|
256
|
+
expect(body.target_version).toBeUndefined();
|
|
257
|
+
expect(body.fixed_in_version).toBeUndefined();
|
|
258
|
+
expect(body.steps_to_reproduce).toBeUndefined();
|
|
259
|
+
expect(body.additional_information).toBeUndefined();
|
|
260
|
+
expect(body.reproducibility).toBeUndefined();
|
|
261
|
+
expect(body.view_state).toBeUndefined();
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it('sends version fields as { name } objects', async () => {
|
|
265
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
266
|
+
makeResponse(201, JSON.stringify({ issue: { id: 301, summary: 'With versions' } }))
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
await mockServer.callTool('create_issue', {
|
|
270
|
+
summary: 'Version test',
|
|
271
|
+
description: 'Version test description.',
|
|
272
|
+
project_id: 1,
|
|
273
|
+
category: 'General',
|
|
274
|
+
version: '1.0.0',
|
|
275
|
+
target_version: '1.1.0',
|
|
276
|
+
fixed_in_version: '1.0.1',
|
|
277
|
+
}, { validate: true });
|
|
278
|
+
|
|
279
|
+
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as Record<string, unknown>;
|
|
280
|
+
expect(body.version).toEqual({ name: '1.0.0' });
|
|
281
|
+
expect(body.target_version).toEqual({ name: '1.1.0' });
|
|
282
|
+
expect(body.fixed_in_version).toEqual({ name: '1.0.1' });
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('sends steps_to_reproduce and additional_information as plain strings', async () => {
|
|
286
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
287
|
+
makeResponse(201, JSON.stringify({ issue: { id: 302, summary: 'With text fields' } }))
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
await mockServer.callTool('create_issue', {
|
|
291
|
+
summary: 'Text fields test',
|
|
292
|
+
description: 'Description.',
|
|
293
|
+
project_id: 1,
|
|
294
|
+
category: 'General',
|
|
295
|
+
steps_to_reproduce: '1. Open app\n2. Click button',
|
|
296
|
+
additional_information: 'Happens only on Windows',
|
|
297
|
+
}, { validate: true });
|
|
298
|
+
|
|
299
|
+
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as Record<string, unknown>;
|
|
300
|
+
expect(body.steps_to_reproduce).toBe('1. Open app\n2. Click button');
|
|
301
|
+
expect(body.additional_information).toBe('Happens only on Windows');
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it('resolves reproducibility to { id } via enum lookup', async () => {
|
|
305
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
306
|
+
makeResponse(201, JSON.stringify({ issue: { id: 303, summary: 'Reproducibility test' } }))
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
await mockServer.callTool('create_issue', {
|
|
310
|
+
summary: 'Repro test',
|
|
311
|
+
description: 'Description.',
|
|
312
|
+
project_id: 1,
|
|
313
|
+
category: 'General',
|
|
314
|
+
reproducibility: 'always',
|
|
315
|
+
}, { validate: true });
|
|
316
|
+
|
|
317
|
+
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as Record<string, unknown>;
|
|
318
|
+
expect(body.reproducibility).toEqual({ id: 10 });
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
it('returns error for unknown reproducibility name', async () => {
|
|
322
|
+
const result = await mockServer.callTool('create_issue', {
|
|
323
|
+
summary: 'Repro test',
|
|
324
|
+
description: 'Description.',
|
|
325
|
+
project_id: 1,
|
|
326
|
+
category: 'General',
|
|
327
|
+
reproducibility: 'immer',
|
|
328
|
+
}, { validate: true });
|
|
329
|
+
|
|
330
|
+
expect(result.isError).toBe(true);
|
|
331
|
+
expect(result.content[0]!.text).toContain('immer');
|
|
332
|
+
expect(result.content[0]!.text).toContain('always');
|
|
333
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
it('returns error for empty reproducibility string', async () => {
|
|
337
|
+
const result = await mockServer.callTool('create_issue', {
|
|
338
|
+
summary: 'Repro test',
|
|
339
|
+
description: 'Description.',
|
|
340
|
+
project_id: 1,
|
|
341
|
+
category: 'General',
|
|
342
|
+
reproducibility: '',
|
|
343
|
+
}, { validate: true });
|
|
344
|
+
|
|
345
|
+
expect(result.isError).toBe(true);
|
|
346
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it('sends view_state as { name } object', async () => {
|
|
350
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
351
|
+
makeResponse(201, JSON.stringify({ issue: { id: 304, summary: 'Private issue' } }))
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
await mockServer.callTool('create_issue', {
|
|
355
|
+
summary: 'Private issue',
|
|
356
|
+
description: 'Description.',
|
|
357
|
+
project_id: 1,
|
|
358
|
+
category: 'General',
|
|
359
|
+
view_state: 'private',
|
|
360
|
+
}, { validate: true });
|
|
361
|
+
|
|
362
|
+
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as Record<string, unknown>;
|
|
363
|
+
expect(body.view_state).toEqual({ name: 'private' });
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
it('rejects invalid view_state values', async () => {
|
|
367
|
+
const result = await mockServer.callTool('create_issue', {
|
|
368
|
+
summary: 'Test',
|
|
369
|
+
description: 'Description.',
|
|
370
|
+
project_id: 1,
|
|
371
|
+
category: 'General',
|
|
372
|
+
view_state: 'restricted',
|
|
373
|
+
}, { validate: true });
|
|
374
|
+
|
|
375
|
+
expect(result.isError).toBe(true);
|
|
376
|
+
expect(fetch).not.toHaveBeenCalled();
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('sends all new optional fields together in a single request', async () => {
|
|
380
|
+
vi.mocked(fetch).mockResolvedValue(
|
|
381
|
+
makeResponse(201, JSON.stringify({ issue: { id: 305, summary: 'Full issue' } }))
|
|
382
|
+
);
|
|
383
|
+
|
|
384
|
+
await mockServer.callTool('create_issue', {
|
|
385
|
+
summary: 'Full issue',
|
|
386
|
+
description: 'Full description.',
|
|
387
|
+
project_id: 1,
|
|
388
|
+
category: 'General',
|
|
389
|
+
version: '2.0.0',
|
|
390
|
+
target_version: '2.1.0',
|
|
391
|
+
fixed_in_version: '2.0.1',
|
|
392
|
+
steps_to_reproduce: 'Step 1',
|
|
393
|
+
additional_information: 'Extra info',
|
|
394
|
+
reproducibility: 'sometimes',
|
|
395
|
+
view_state: 'public',
|
|
396
|
+
}, { validate: true });
|
|
397
|
+
|
|
398
|
+
expect(fetch).toHaveBeenCalledTimes(1);
|
|
399
|
+
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as Record<string, unknown>;
|
|
400
|
+
expect(body.version).toEqual({ name: '2.0.0' });
|
|
401
|
+
expect(body.target_version).toEqual({ name: '2.1.0' });
|
|
402
|
+
expect(body.fixed_in_version).toEqual({ name: '2.0.1' });
|
|
403
|
+
expect(body.steps_to_reproduce).toBe('Step 1');
|
|
404
|
+
expect(body.additional_information).toBe('Extra info');
|
|
405
|
+
expect(body.reproducibility).toEqual({ id: 30 });
|
|
406
|
+
expect(body.view_state).toEqual({ name: 'public' });
|
|
407
|
+
});
|
|
214
408
|
});
|
|
215
409
|
|
|
216
410
|
// ---------------------------------------------------------------------------
|
|
@@ -228,7 +422,7 @@ describe('create_issue – handler username', () => {
|
|
|
228
422
|
);
|
|
229
423
|
|
|
230
424
|
await server.callTool('create_issue', {
|
|
231
|
-
summary: 'Test', project_id: 1, category: 'General', handler: 'dom',
|
|
425
|
+
summary: 'Test', description: 'Test description.', project_id: 1, category: 'General', handler: 'dom',
|
|
232
426
|
}, { validate: true });
|
|
233
427
|
|
|
234
428
|
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as { handler: { id: number } };
|
|
@@ -245,7 +439,7 @@ describe('create_issue – handler username', () => {
|
|
|
245
439
|
);
|
|
246
440
|
|
|
247
441
|
await server.callTool('create_issue', {
|
|
248
|
-
summary: 'Test', project_id: 1, category: 'General', handler: 'John Doe',
|
|
442
|
+
summary: 'Test', description: 'Test description.', project_id: 1, category: 'General', handler: 'John Doe',
|
|
249
443
|
}, { validate: true });
|
|
250
444
|
|
|
251
445
|
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as { handler: { id: number } };
|
|
@@ -262,7 +456,7 @@ describe('create_issue – handler username', () => {
|
|
|
262
456
|
.mockResolvedValueOnce(makeResponse(201, JSON.stringify({ issue: { id: 202, summary: 'New' } })));
|
|
263
457
|
|
|
264
458
|
await server.callTool('create_issue', {
|
|
265
|
-
summary: 'Test', project_id: 1, category: 'General', handler: 'alice',
|
|
459
|
+
summary: 'Test', description: 'Test description.', project_id: 1, category: 'General', handler: 'alice',
|
|
266
460
|
}, { validate: true });
|
|
267
461
|
|
|
268
462
|
const projectUsersCall = vi.mocked(fetch).mock.calls[0]![0] as string;
|
|
@@ -278,7 +472,7 @@ describe('create_issue – handler username', () => {
|
|
|
278
472
|
registerIssueTools(server as never, client, cache);
|
|
279
473
|
|
|
280
474
|
const result = await server.callTool('create_issue', {
|
|
281
|
-
summary: 'Test', project_id: 1, category: 'General', handler: 'nonexistent',
|
|
475
|
+
summary: 'Test', description: 'Test description.', project_id: 1, category: 'General', handler: 'nonexistent',
|
|
282
476
|
});
|
|
283
477
|
|
|
284
478
|
expect(result.isError).toBe(true);
|
|
@@ -297,7 +491,7 @@ describe('create_issue – handler username', () => {
|
|
|
297
491
|
);
|
|
298
492
|
|
|
299
493
|
await server.callTool('create_issue', {
|
|
300
|
-
summary: 'Test', project_id: 1, category: 'General', handler_id: 99, handler: 'dom',
|
|
494
|
+
summary: 'Test', description: 'Test description.', project_id: 1, category: 'General', handler_id: 99, handler: 'dom',
|
|
301
495
|
}, { validate: true });
|
|
302
496
|
|
|
303
497
|
const body = JSON.parse(vi.mocked(fetch).mock.calls[0]![1]!.body as string) as { handler: { id: number } };
|