@dpesch/mantisbt-mcp-server 1.14.0 → 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 +57 -0
- package/README.de.md +118 -29
- package/README.md +118 -29
- 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/embedder.js +49 -14
- package/dist/search/index.js +18 -11
- package/dist/search/store.js +30 -7
- package/dist/search/sync-lock.js +66 -0
- package/dist/search/sync.js +67 -9
- package/dist/search/tools.js +21 -27
- 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 +605 -184
- package/docs/cookbook.md +605 -184
- 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/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,8 +28,18 @@ function errorText(msg) {
|
|
|
25
28
|
// ---------------------------------------------------------------------------
|
|
26
29
|
// registerSearchTools
|
|
27
30
|
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
export function
|
|
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
|
+
}
|
|
42
|
+
export function registerSearchTools(server, client, store, embedder, lockDir) {
|
|
30
43
|
// ---------------------------------------------------------------------------
|
|
31
44
|
// search_issues
|
|
32
45
|
// ---------------------------------------------------------------------------
|
|
@@ -107,7 +120,7 @@ export function registerSearchTools(server, client, store, embedder) {
|
|
|
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) {
|
|
|
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) {
|
|
|
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) {
|
|
@@ -214,13 +212,9 @@ export function registerSearchTools(server, client, store, embedder) {
|
|
|
214
212
|
},
|
|
215
213
|
}, async ({ project_id, full }) => {
|
|
216
214
|
try {
|
|
217
|
-
if (full) {
|
|
218
|
-
await store.clear();
|
|
219
|
-
await store.resetLastSyncedAt();
|
|
220
|
-
}
|
|
221
215
|
const startMs = Date.now();
|
|
222
|
-
const syncService = new SearchSyncService(client, store, embedder);
|
|
223
|
-
const { indexed, skipped, total } = await syncService.sync(project_id);
|
|
216
|
+
const syncService = new SearchSyncService(client, store, embedder, lockDir);
|
|
217
|
+
const { indexed, skipped, total } = await syncService.sync(project_id, { full });
|
|
224
218
|
const duration_ms = Date.now() - startMs;
|
|
225
219
|
return {
|
|
226
220
|
content: [
|
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
|
+
}
|