@dpesch/mantisbt-mcp-server 1.14.1 → 1.15.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 +45 -0
- package/README.de.md +117 -28
- package/README.md +117 -28
- package/dist/client.js +4 -0
- package/dist/coerce.js +8 -0
- package/dist/config.js +3 -0
- package/dist/deprecation.js +9 -0
- package/dist/index.js +20 -31
- package/dist/issue-enrich.js +23 -0
- package/dist/issue-envelope.js +4 -0
- package/dist/prompts/index.js +4 -4
- package/dist/resources/index.js +4 -8
- package/dist/search/index.js +9 -7
- package/dist/search/tools.js +18 -20
- package/dist/tools/config.js +9 -4
- package/dist/tools/files.js +12 -5
- package/dist/tools/index.js +34 -0
- package/dist/tools/issues.js +285 -147
- package/dist/tools/metadata.js +99 -40
- package/dist/tools/monitors.js +145 -20
- package/dist/tools/notes.js +67 -9
- package/dist/tools/project-versions.js +40 -18
- package/dist/tools/projects.js +109 -37
- package/dist/tools/users.js +3 -1
- package/dist/tools/version.js +61 -35
- package/dist/toolsets.js +104 -0
- package/docs/cookbook.de.md +600 -181
- package/docs/cookbook.md +600 -181
- package/docs/examples.de.md +27 -3
- package/docs/examples.md +27 -3
- package/package.json +2 -1
- package/server.json +2 -2
package/dist/index.js
CHANGED
|
@@ -10,19 +10,8 @@ import { getConfig, getStartupConfig, assertHttpAuthConfigured } from './config.
|
|
|
10
10
|
import { MantisClient } from './client.js';
|
|
11
11
|
import { VersionHintService, setGlobalVersionHint } from './version-hint.js';
|
|
12
12
|
import { MetadataCache } from './cache.js';
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { registerFileTools } from './tools/files.js';
|
|
16
|
-
import { registerRelationshipTools } from './tools/relationships.js';
|
|
17
|
-
import { registerMonitorTools } from './tools/monitors.js';
|
|
18
|
-
import { registerProjectTools } from './tools/projects.js';
|
|
19
|
-
import { registerProjectVersionTools } from './tools/project-versions.js';
|
|
20
|
-
import { registerUserTools } from './tools/users.js';
|
|
21
|
-
import { registerFilterTools } from './tools/filters.js';
|
|
22
|
-
import { registerConfigTools } from './tools/config.js';
|
|
23
|
-
import { registerMetadataTools } from './tools/metadata.js';
|
|
24
|
-
import { registerTagTools } from './tools/tags.js';
|
|
25
|
-
import { registerVersionTools } from './tools/version.js';
|
|
13
|
+
import { registerAllTools } from './tools/index.js';
|
|
14
|
+
import { filterToolRegistration } from './toolsets.js';
|
|
26
15
|
import { registerPrompts } from './prompts/index.js';
|
|
27
16
|
import { registerResources } from './resources/index.js';
|
|
28
17
|
// ---------------------------------------------------------------------------
|
|
@@ -55,26 +44,26 @@ async function createMcpServer(transport) {
|
|
|
55
44
|
name: 'mantisbt-mcp-server',
|
|
56
45
|
version,
|
|
57
46
|
});
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
47
|
+
// Tools register through a filter for MANTIS_TOOLSETS / MANTIS_DISABLE_DESTRUCTIVE;
|
|
48
|
+
// prompts and resources use the server directly.
|
|
49
|
+
const tools = filterToolRegistration(server, startupConfig);
|
|
50
|
+
// Optional: Semantic search module — needs MANTIS_SEARCH_ENABLED=true and the
|
|
51
|
+
// "search" tool group (skipping it avoids loading the model and the index).
|
|
52
|
+
// Initialized first so get_metadata can report the index status.
|
|
53
|
+
const searchIndexStatus = startupConfig.search.enabled && startupConfig.toolsets.has('search')
|
|
54
|
+
? await (await import('./search/index.js')).initializeSearchModule(tools, client, startupConfig.search)
|
|
55
|
+
: undefined;
|
|
56
|
+
registerAllTools(tools, {
|
|
57
|
+
client,
|
|
58
|
+
cache,
|
|
59
|
+
versionHint,
|
|
60
|
+
version,
|
|
61
|
+
uploadDir: startupConfig.uploadDir,
|
|
62
|
+
transport,
|
|
63
|
+
searchIndexStatus,
|
|
64
|
+
});
|
|
71
65
|
registerPrompts(server);
|
|
72
66
|
registerResources(server, client, cache, startupConfig.testEnvironment);
|
|
73
|
-
// Optional: Semantic search module
|
|
74
|
-
if (startupConfig.search.enabled) {
|
|
75
|
-
const { initializeSearchModule } = await import('./search/index.js');
|
|
76
|
-
await initializeSearchModule(server, client, startupConfig.search);
|
|
77
|
-
}
|
|
78
67
|
return server;
|
|
79
68
|
}
|
|
80
69
|
// ---------------------------------------------------------------------------
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { buildAttachmentDownloadUrl, buildIssueViewUrl, buildNoteViewUrl } from './client.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adds the links MantisBT's REST API leaves out: view_url for the issue and
|
|
4
|
+
* each note, download_url for each attachment (REST sends it only via SOAP).
|
|
5
|
+
* issueId is the fallback when a select projection excluded the id field.
|
|
6
|
+
*/
|
|
7
|
+
export function enrichIssue(issue, baseUrl, issueId) {
|
|
8
|
+
const id = issue.id ?? issueId;
|
|
9
|
+
if (id === undefined)
|
|
10
|
+
return issue;
|
|
11
|
+
return {
|
|
12
|
+
...issue,
|
|
13
|
+
view_url: buildIssueViewUrl(baseUrl, id),
|
|
14
|
+
notes: issue.notes?.map(note => ({
|
|
15
|
+
...note,
|
|
16
|
+
view_url: buildNoteViewUrl(baseUrl, id, note.id),
|
|
17
|
+
})),
|
|
18
|
+
attachments: issue.attachments?.map(file => ({
|
|
19
|
+
...file,
|
|
20
|
+
download_url: buildAttachmentDownloadUrl(baseUrl, file.id),
|
|
21
|
+
})),
|
|
22
|
+
};
|
|
23
|
+
}
|
package/dist/prompts/index.js
CHANGED
|
@@ -16,7 +16,7 @@ export function registerPrompts(server) {
|
|
|
16
16
|
description: 'Guide the creation of a complete bug report in MantisBT. Project, category, summary, and description are required.',
|
|
17
17
|
argsSchema: {
|
|
18
18
|
project_id: z.coerce.number().int().positive().describe('Numeric project ID'),
|
|
19
|
-
category: z.string().describe('Issue category (call
|
|
19
|
+
category: z.string().describe('Issue category (call get_project with include: ["categories"] to list valid values)'),
|
|
20
20
|
summary: z.string().describe('Short, descriptive summary of the bug'),
|
|
21
21
|
description: z.string().describe('Detailed description of the bug'),
|
|
22
22
|
steps_to_reproduce: z.string().optional().describe('Step-by-step reproduction instructions'),
|
|
@@ -46,7 +46,7 @@ export function registerPrompts(server) {
|
|
|
46
46
|
if (environment) {
|
|
47
47
|
lines.push(``, `Environment:`, environment);
|
|
48
48
|
}
|
|
49
|
-
lines.push(``, `Use the create_issue tool to submit this bug report.`, `Call
|
|
49
|
+
lines.push(``, `Use the create_issue tool to submit this bug report.`, `Call get_issue_schema first if you need to look up valid severity and priority values.`, `Default to severity "minor" and priority "normal" unless the user specifies otherwise.`);
|
|
50
50
|
return userMessage(lines.join('\n'));
|
|
51
51
|
});
|
|
52
52
|
// ---------------------------------------------------------------------------
|
|
@@ -57,7 +57,7 @@ export function registerPrompts(server) {
|
|
|
57
57
|
description: 'Guide the creation of a feature request in MantisBT. Project, category, summary, and description are required.',
|
|
58
58
|
argsSchema: {
|
|
59
59
|
project_id: z.coerce.number().int().positive().describe('Numeric project ID'),
|
|
60
|
-
category: z.string().describe('Issue category (call
|
|
60
|
+
category: z.string().describe('Issue category (call get_project with include: ["categories"] to list valid values)'),
|
|
61
61
|
summary: z.string().describe('Short description of the requested feature'),
|
|
62
62
|
description: z.string().describe('Detailed description of the feature'),
|
|
63
63
|
use_case: z.string().optional().describe('Business use case or motivation for the feature'),
|
|
@@ -75,7 +75,7 @@ export function registerPrompts(server) {
|
|
|
75
75
|
if (use_case) {
|
|
76
76
|
lines.push(``, `Use case / motivation:`, use_case);
|
|
77
77
|
}
|
|
78
|
-
lines.push(``, `Use the create_issue tool to submit this feature request.`, `Call
|
|
78
|
+
lines.push(``, `Use the create_issue tool to submit this feature request.`, `Call get_issue_schema first to look up valid severity values — use severity "feature" and priority "normal" unless the user specifies otherwise.`);
|
|
79
79
|
return userMessage(lines.join('\n'));
|
|
80
80
|
});
|
|
81
81
|
// ---------------------------------------------------------------------------
|
package/dist/resources/index.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { fetchIssueEnums } from '../tools/config.js';
|
|
3
|
-
import { normalizeProject } from '../tools/metadata.js';
|
|
4
|
-
import { ALL_PROJECTS_PREFIX } from '../constants.js';
|
|
3
|
+
import { normalizeProject, stripAllProjectsPrefix } from '../tools/metadata.js';
|
|
5
4
|
function jsonResource(uri, data) {
|
|
6
5
|
return {
|
|
7
6
|
contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data) }],
|
|
@@ -21,7 +20,7 @@ export function registerResources(server, client, cache, testEnvironment = false
|
|
|
21
20
|
}, async (uri) => jsonResource(uri, await client.get('users/me')));
|
|
22
21
|
server.registerResource('projects', 'mantis://projects', {
|
|
23
22
|
title: 'Projects',
|
|
24
|
-
description: 'All MantisBT projects accessible to the current API user. Served from local cache when fresh; falls back to live fetch. Refresh via
|
|
23
|
+
description: 'All MantisBT projects accessible to the current API user. Served from local cache when fresh; falls back to live fetch. Refresh via get_metadata with refresh: true.',
|
|
25
24
|
mimeType: 'application/json',
|
|
26
25
|
}, async (uri) => jsonResource(uri, await loadProjects()));
|
|
27
26
|
server.registerResource('project-detail', new ResourceTemplate('mantis://projects/{id}', {
|
|
@@ -40,7 +39,7 @@ export function registerResources(server, client, cache, testEnvironment = false
|
|
|
40
39
|
},
|
|
41
40
|
}), {
|
|
42
41
|
title: 'Project Detail',
|
|
43
|
-
description: 'Combined project view: fields (status, view_state, access_level, description) plus all associated users, versions, and categories. Served from local cache when fresh; falls back to live API fetch. Refresh via
|
|
42
|
+
description: 'Combined project view: fields (status, view_state, access_level, description) plus all associated users, versions, and categories. Served from local cache when fresh; falls back to live API fetch. Refresh via get_metadata with refresh: true.',
|
|
44
43
|
mimeType: 'application/json',
|
|
45
44
|
}, async (uri, { id }) => {
|
|
46
45
|
const numId = Number(id);
|
|
@@ -66,10 +65,7 @@ export function registerResources(server, client, cache, testEnvironment = false
|
|
|
66
65
|
project = raw ? normalizeProject(raw) : undefined;
|
|
67
66
|
users = usersResult.users ?? [];
|
|
68
67
|
versions = versionsResult.versions ?? [];
|
|
69
|
-
categories = (raw?.categories ?? [])
|
|
70
|
-
...c,
|
|
71
|
-
name: c.name.startsWith(ALL_PROJECTS_PREFIX) ? c.name.slice(ALL_PROJECTS_PREFIX.length) : c.name,
|
|
72
|
-
}));
|
|
68
|
+
categories = stripAllProjectsPrefix(raw?.categories ?? []);
|
|
73
69
|
}
|
|
74
70
|
if (!project) {
|
|
75
71
|
throw new Error(`Project ${numId} not found`);
|
package/dist/search/index.js
CHANGED
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
import { createVectorStore } from './store.js';
|
|
2
2
|
import { Embedder } from './embedder.js';
|
|
3
|
-
import { registerSearchTools } from './tools.js';
|
|
3
|
+
import { registerSearchTools, getSearchIndexStatus } from './tools.js';
|
|
4
4
|
import { SearchSyncService, SyncLockedError } from './sync.js';
|
|
5
5
|
// ---------------------------------------------------------------------------
|
|
6
6
|
// initializeSearchModule
|
|
7
7
|
// ---------------------------------------------------------------------------
|
|
8
8
|
export async function initializeSearchModule(server, client, config) {
|
|
9
|
-
if (!config.enabled)
|
|
10
|
-
return;
|
|
11
9
|
const store = createVectorStore(config.backend, config.dir);
|
|
12
10
|
const embedder = new Embedder(config.modelName, config.numThreads);
|
|
13
11
|
registerSearchTools(server, client, store, embedder, config.dir);
|
|
14
|
-
// Pre-initialize lastKnownTotal so
|
|
12
|
+
// Pre-initialize lastKnownTotal so the index status (get_metadata) shows a value
|
|
15
13
|
// immediately on startup, even while the background sync is still running.
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
14
|
+
// count() loads the whole index into memory, and this runs before the server
|
|
15
|
+
// connects — so only do it when there is no stored total yet.
|
|
16
|
+
if (await store.getLastKnownTotal() === null) {
|
|
17
|
+
const startupCount = await store.count();
|
|
18
|
+
if (startupCount > 0)
|
|
19
|
+
await store.setLastKnownTotal(startupCount);
|
|
19
20
|
}
|
|
20
21
|
// Non-blocking background sync on startup. If another server process (another
|
|
21
22
|
// client session) is already syncing the shared index, this one skips it.
|
|
@@ -27,4 +28,5 @@ export async function initializeSearchModule(server, client, config) {
|
|
|
27
28
|
}
|
|
28
29
|
console.error('[mantisbt-search] sync error:', err instanceof Error ? err.message : String(err));
|
|
29
30
|
});
|
|
31
|
+
return () => getSearchIndexStatus(store);
|
|
30
32
|
}
|
package/dist/search/tools.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
+
import { coerceBool } from '../coerce.js';
|
|
2
3
|
import { buildIssueViewUrl } from '../client.js';
|
|
3
4
|
import { SearchSyncService } from './sync.js';
|
|
4
5
|
import { getVersionHint } from '../version-hint.js';
|
|
6
|
+
import { firstIssue } from '../issue-envelope.js';
|
|
7
|
+
import { deprecationNote } from '../deprecation.js';
|
|
5
8
|
import { dateFilterSchema, matchesDateFilter, hasDateFilter } from '../date-filter.js';
|
|
6
9
|
import { extractTerms, highlightText, extractSnippet, hasTermMatch } from './highlight.js';
|
|
7
10
|
function buildHighlights(summary, description, terms) {
|
|
@@ -25,7 +28,17 @@ function errorText(msg) {
|
|
|
25
28
|
// ---------------------------------------------------------------------------
|
|
26
29
|
// registerSearchTools
|
|
27
30
|
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
31
|
+
/** Fill level of the search index (shared by get_metadata and get_search_index_status). */
|
|
32
|
+
export async function getSearchIndexStatus(store) {
|
|
33
|
+
const [indexed, lastSyncedAt, total] = await Promise.all([
|
|
34
|
+
store.count(),
|
|
35
|
+
store.getLastSyncedAt(),
|
|
36
|
+
store.getLastKnownTotal(),
|
|
37
|
+
]);
|
|
38
|
+
const percent = total !== null ? (total > 0 ? Math.round((indexed / total) * 100) : 0) : null;
|
|
39
|
+
const summary = total !== null ? `${indexed}/${total} (${percent} %)` : `${indexed}/? (total unknown)`;
|
|
40
|
+
return { summary, indexed, total, percent, last_synced_at: lastSyncedAt };
|
|
41
|
+
}
|
|
29
42
|
export function registerSearchTools(server, client, store, embedder, lockDir) {
|
|
30
43
|
// ---------------------------------------------------------------------------
|
|
31
44
|
// search_issues
|
|
@@ -107,7 +120,7 @@ export function registerSearchTools(server, client, store, embedder, lockDir) {
|
|
|
107
120
|
const enriched = await Promise.all(results.map(async ({ id, score }) => {
|
|
108
121
|
try {
|
|
109
122
|
const issueResult = await client.get(`issues/${id}`);
|
|
110
|
-
const issue = issueResult
|
|
123
|
+
const issue = (firstIssue(issueResult) ?? {});
|
|
111
124
|
if (filterActive && !matchesDateFilter(issue, dateFilter)) {
|
|
112
125
|
return null;
|
|
113
126
|
}
|
|
@@ -153,7 +166,8 @@ export function registerSearchTools(server, client, store, embedder, lockDir) {
|
|
|
153
166
|
// ---------------------------------------------------------------------------
|
|
154
167
|
server.registerTool('get_search_index_status', {
|
|
155
168
|
title: 'Search Index Status',
|
|
156
|
-
description: '
|
|
169
|
+
description: `${deprecationNote('get_search_index_status', 'get_metadata')}\n\n` +
|
|
170
|
+
'Returns the current fill level of the semantic search index: how many issues are ' +
|
|
157
171
|
'indexed vs. the total number of issues in MantisBT, plus the timestamp of the last sync.',
|
|
158
172
|
inputSchema: z.object({}),
|
|
159
173
|
annotations: {
|
|
@@ -163,24 +177,8 @@ export function registerSearchTools(server, client, store, embedder, lockDir) {
|
|
|
163
177
|
},
|
|
164
178
|
}, async () => {
|
|
165
179
|
try {
|
|
166
|
-
const [indexed, lastSyncedAt, total] = await Promise.all([
|
|
167
|
-
store.count(),
|
|
168
|
-
store.getLastSyncedAt(),
|
|
169
|
-
store.getLastKnownTotal(),
|
|
170
|
-
]);
|
|
171
|
-
const percent = total !== null
|
|
172
|
-
? (total > 0 ? Math.round((indexed / total) * 100) : 0)
|
|
173
|
-
: null;
|
|
174
|
-
const summary = total !== null
|
|
175
|
-
? `${indexed}/${total} (${percent} %)`
|
|
176
|
-
: `${indexed}/? (total unknown)`;
|
|
177
180
|
return {
|
|
178
|
-
content: [
|
|
179
|
-
{
|
|
180
|
-
type: 'text',
|
|
181
|
-
text: JSON.stringify({ summary, indexed, total, percent, last_synced_at: lastSyncedAt }, null, 2),
|
|
182
|
-
},
|
|
183
|
-
],
|
|
181
|
+
content: [{ type: 'text', text: JSON.stringify(await getSearchIndexStatus(store), null, 2) }],
|
|
184
182
|
};
|
|
185
183
|
}
|
|
186
184
|
catch (error) {
|
package/dist/tools/config.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { ISSUE_ENUM_OPTIONS, MANTIS_CANONICAL_ENUM_NAMES } from '../constants.js';
|
|
3
3
|
import { getVersionHint } from '../version-hint.js';
|
|
4
|
+
import { deprecationNote } from '../deprecation.js';
|
|
4
5
|
function errorText(msg) {
|
|
5
6
|
const vh = getVersionHint();
|
|
6
7
|
vh?.triggerLatestVersionFetch();
|
|
@@ -127,7 +128,9 @@ Common option names:
|
|
|
127
128
|
// ---------------------------------------------------------------------------
|
|
128
129
|
server.registerTool('get_issue_enums', {
|
|
129
130
|
title: 'Get Issue Enum Values',
|
|
130
|
-
description:
|
|
131
|
+
description: `${deprecationNote('get_issue_enums', 'get_issue_schema')}
|
|
132
|
+
|
|
133
|
+
Return valid ID, name, and (if available) localized label for all issue enum fields.
|
|
131
134
|
|
|
132
135
|
Use this tool before creating or updating issues to look up the correct value
|
|
133
136
|
for severity, status, priority, resolution, or reproducibility.
|
|
@@ -189,7 +192,9 @@ because there is no separate English original.`,
|
|
|
189
192
|
// ---------------------------------------------------------------------------
|
|
190
193
|
server.registerTool('list_languages', {
|
|
191
194
|
title: 'List Supported Languages',
|
|
192
|
-
description: '
|
|
195
|
+
description: `${deprecationNote('list_languages', 'get_current_user (field "language")')}
|
|
196
|
+
|
|
197
|
+
Despite its name, MantisBT's GET /lang returns only the language of the current user ({ strings, language }), not a list of all languages.`,
|
|
193
198
|
inputSchema: z.object({}),
|
|
194
199
|
annotations: {
|
|
195
200
|
readOnlyHint: true,
|
|
@@ -217,7 +222,7 @@ because there is no separate English original.`,
|
|
|
217
222
|
|
|
218
223
|
The MantisBT REST API exposes a GET /tags endpoint on some installations.
|
|
219
224
|
If that endpoint is not available, this tool falls back to the local metadata
|
|
220
|
-
cache populated by
|
|
225
|
+
cache populated by get_metadata.`,
|
|
221
226
|
inputSchema: z.object({
|
|
222
227
|
page: z.coerce.number().int().positive().default(1).describe('Page number (default: 1)'),
|
|
223
228
|
page_size: z.coerce.number().int().min(1).max(200).default(50).describe('Tags per page (default: 50)'),
|
|
@@ -252,7 +257,7 @@ cache populated by sync_metadata.`,
|
|
|
252
257
|
return {
|
|
253
258
|
content: [{
|
|
254
259
|
type: 'text',
|
|
255
|
-
text: `Error: ${msg}\n\nThe GET /tags endpoint is not available in this MantisBT installation. No cached tags found either — run
|
|
260
|
+
text: `Error: ${msg}\n\nThe GET /tags endpoint is not available in this MantisBT installation. No cached tags found either — run get_metadata with refresh: true to populate the cache if your installation provides this endpoint.`,
|
|
256
261
|
}],
|
|
257
262
|
isError: true,
|
|
258
263
|
};
|
package/dist/tools/files.js
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { readFile } from 'node:fs/promises';
|
|
2
2
|
import { basename, resolve, sep } from 'node:path';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import { enrichIssue } from '../issue-enrich.js';
|
|
4
5
|
import { getVersionHint } from '../version-hint.js';
|
|
6
|
+
import { firstIssue } from '../issue-envelope.js';
|
|
7
|
+
import { deprecationNote } from '../deprecation.js';
|
|
5
8
|
function errorText(msg) {
|
|
6
9
|
const vh = getVersionHint();
|
|
7
10
|
vh?.triggerLatestVersionFetch();
|
|
@@ -21,9 +24,9 @@ export function registerFileTools(server, client, uploadDir, transport = 'stdio'
|
|
|
21
24
|
// ---------------------------------------------------------------------------
|
|
22
25
|
server.registerTool('list_issue_files', {
|
|
23
26
|
title: 'List Issue File Attachments',
|
|
24
|
-
description:
|
|
27
|
+
description: `${deprecationNote('list_issue_files', 'get_issue with select: "attachments"')}
|
|
25
28
|
|
|
26
|
-
|
|
29
|
+
List all file attachments of a MantisBT issue. Returns an array of attachment objects, each containing id, filename, size in bytes, content_type, and download_url. Returns an empty array if the issue has no attachments.`,
|
|
27
30
|
inputSchema: z.object({
|
|
28
31
|
issue_id: z.coerce.number().int().positive().describe('Numeric issue ID'),
|
|
29
32
|
}),
|
|
@@ -34,8 +37,12 @@ Use this tool when you need to inspect or enumerate files attached to an issue.
|
|
|
34
37
|
},
|
|
35
38
|
}, async ({ issue_id }) => {
|
|
36
39
|
try {
|
|
37
|
-
const result = await
|
|
38
|
-
|
|
40
|
+
const [result, baseUrl] = await Promise.all([
|
|
41
|
+
client.get(`issues/${issue_id}`, { select: 'id,attachments' }),
|
|
42
|
+
client.getBaseUrl(),
|
|
43
|
+
]);
|
|
44
|
+
const issue = firstIssue(result);
|
|
45
|
+
const attachments = (issue && enrichIssue(issue, baseUrl, issue_id).attachments) ?? [];
|
|
39
46
|
return {
|
|
40
47
|
content: [{ type: 'text', text: JSON.stringify(attachments, null, 2) }],
|
|
41
48
|
};
|
|
@@ -58,7 +65,7 @@ Provide exactly one of the two input modes:
|
|
|
58
65
|
|
|
59
66
|
The optional content_type sets the MIME type (e.g. "image/png"); defaults to "application/octet-stream". Use the optional description to annotate the attachment.
|
|
60
67
|
|
|
61
|
-
Use this tool to attach files such as logs, screenshots, or patches to an existing issue. To list existing attachments, use
|
|
68
|
+
Use this tool to attach files such as logs, screenshots, or patches to an existing issue. To list existing attachments, use get_issue with select: "attachments".`,
|
|
62
69
|
inputSchema: z.object({
|
|
63
70
|
issue_id: z.coerce.number().int().positive().describe('Numeric issue ID'),
|
|
64
71
|
file_path: z.string().min(1).optional().describe('Preferred: absolute path to the local file to upload — use this whenever the file exists on disk (mutually exclusive with content)'),
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { registerIssueTools } from './issues.js';
|
|
2
|
+
import { registerNoteTools } from './notes.js';
|
|
3
|
+
import { registerFileTools } from './files.js';
|
|
4
|
+
import { registerRelationshipTools } from './relationships.js';
|
|
5
|
+
import { registerMonitorTools } from './monitors.js';
|
|
6
|
+
import { registerProjectTools } from './projects.js';
|
|
7
|
+
import { registerProjectVersionTools } from './project-versions.js';
|
|
8
|
+
import { registerUserTools } from './users.js';
|
|
9
|
+
import { registerFilterTools } from './filters.js';
|
|
10
|
+
import { registerConfigTools } from './config.js';
|
|
11
|
+
import { registerMetadataTools } from './metadata.js';
|
|
12
|
+
import { registerTagTools } from './tags.js';
|
|
13
|
+
import { registerVersionTools } from './version.js';
|
|
14
|
+
/**
|
|
15
|
+
* Registers every tool except the optional search tools, which the search
|
|
16
|
+
* module registers itself. Shared by createMcpServer and the test that checks
|
|
17
|
+
* TOOL_GROUPS against the registered tools, so both see the same wiring.
|
|
18
|
+
*/
|
|
19
|
+
export function registerAllTools(server, deps) {
|
|
20
|
+
const { client, cache } = deps;
|
|
21
|
+
registerIssueTools(server, client, cache);
|
|
22
|
+
registerNoteTools(server, client);
|
|
23
|
+
registerFileTools(server, client, deps.uploadDir, deps.transport);
|
|
24
|
+
registerRelationshipTools(server, client);
|
|
25
|
+
registerMonitorTools(server, client);
|
|
26
|
+
registerProjectTools(server, client, cache);
|
|
27
|
+
registerProjectVersionTools(server, client, cache);
|
|
28
|
+
registerUserTools(server, client);
|
|
29
|
+
registerFilterTools(server, client);
|
|
30
|
+
registerConfigTools(server, client, cache);
|
|
31
|
+
registerMetadataTools(server, client, cache, deps.searchIndexStatus);
|
|
32
|
+
registerTagTools(server, client);
|
|
33
|
+
registerVersionTools(server, client, deps.versionHint, deps.version);
|
|
34
|
+
}
|