@dpesch/mantisbt-mcp-server 1.7.0 → 1.8.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 CHANGED
@@ -7,6 +7,26 @@ This project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  ---
9
9
 
10
+ ## [Unreleased]
11
+
12
+ ---
13
+
14
+ ## [1.8.0] – 2026-03-27
15
+
16
+ ### Added
17
+ - New MCP resource `mantis://projects/{id}`: combined project view with fields (`status`, `view_state`, `access_level`, `description`) plus users, versions, and categories — data that was previously only accessible via separate tool calls. Served from local cache; falls back to three parallel API calls when the cache is cold.
18
+ - New tool `find_project_member`: search users with access to a project by name, display name, or email. Case-insensitive substring matching; returns up to `limit` results (default 10, max 100). Served from local cache when fresh; falls back to a live API call otherwise.
19
+ - New tool `get_metadata_full`: returns the complete raw metadata cache (all project fields, full user/version/category lists, all tags) as minified JSON. Use when the compact summary from `get_metadata` is not enough.
20
+
21
+ ### Changed
22
+ - `get_metadata` now returns a compact summary (project count, tag count, per-project counts of users/versions/categories, cache timestamp, and remaining TTL) instead of the full raw cache dump. Reduces response size from hundreds of KB to a few hundred bytes. Use `list_projects` for the full project list, `get_project_users` / `get_project_versions` / `get_project_categories` for per-project data, and `list_tags` for tags.
23
+ - `list_projects` now applies the same normalization as `sync_metadata`: `custom_fields` and other undeclared API fields are stripped from project objects, keeping the response lean and consistent.
24
+
25
+ ### Fixed
26
+ - `mantis://projects` resource response reduced from ~270 KB to ~6 KB (98% smaller): `custom_fields` are no longer included in project objects (they were passed through from the raw MantisBT API response despite not being part of the project schema), JSON output is now minified, and enum sub-objects (`status`, `view_state`, `access_level`) are stripped to only their declared fields. The `label` field on `status` and `view_state` is preserved for localized display name lookups.
27
+
28
+ ---
29
+
10
30
  ## [1.7.0] – 2026-03-26
11
31
 
12
32
  ### Added
package/README.de.md CHANGED
@@ -133,10 +133,11 @@ npm run build
133
133
 
134
134
  | Tool | Beschreibung |
135
135
  |---|---|
136
- | `list_projects` | Alle zugänglichen Projekte auflisten |
136
+ | `list_projects` | Alle zugänglichen Projekte auflisten; gibt normalisierte Projektdaten zurück (konsistent mit dem `sync_metadata`-Cache) |
137
137
  | `get_project_versions` | Versionen eines Projekts abrufen; optionale Booleans `obsolete` und `inherit` für veraltete bzw. vom Elternprojekt geerbte Versionen |
138
138
  | `get_project_categories` | Kategorien eines Projekts abrufen |
139
139
  | `get_project_users` | Benutzer eines Projekts abrufen |
140
+ | `find_project_member` | Projektmitglieder nach Name, Realname oder E-Mail suchen (Teilstring-Suche, Groß-/Kleinschreibung ignoriert); optionale Parameter `query` und `limit` (Standard 10, max. 100); Cache-first |
140
141
 
141
142
  ### Semantische Suche *(optional)*
142
143
 
@@ -177,7 +178,8 @@ npm install sqlite-vec better-sqlite3
177
178
  | Tool | Beschreibung |
178
179
  |---|---|
179
180
  | `get_issue_fields` | Alle gültigen Feldnamen für den `select`-Parameter von `list_issues` zurückgeben |
180
- | `get_metadata` | Gecachte Metadaten abrufen (Projekte, Benutzer, Versionen, Kategorien) |
181
+ | `get_metadata` | Kompakte Metadaten-Zusammenfassung abrufen: Anzahl Projekte/Tags und Anzahl Benutzer/Versionen/Kategorien je Projekt; für vollständige Arrays `get_metadata_full` verwenden |
182
+ | `get_metadata_full` | Vollständigen rohen Metadaten-Cache als minifiziertes JSON zurückgeben (alle Projekte mit vollständigen Feldern, Benutzer/Versionen/Kategorien je Projekt, alle Tags) |
181
183
  | `sync_metadata` | Metadaten-Cache neu befüllen |
182
184
  | `list_filters` | Gespeicherte Filter auflisten |
183
185
  | `get_current_user` | Eigenes Benutzerprofil abrufen |
@@ -194,7 +196,8 @@ MCP-Ressourcen sind URI-adressierbare, schreibgeschützte Daten, die Clients dir
194
196
  | Ressource-URI | Beschreibung |
195
197
  |---|---|
196
198
  | `mantis://me` | Profil des authentifizierten API-Benutzers (Live-Abruf) |
197
- | `mantis://projects` | Alle zugänglichen MantisBT-Projekte (Cache-basiert, Aktualisierung via `sync_metadata`) |
199
+ | `mantis://projects` | Alle zugänglichen MantisBT-Projekte als kompakte Liste (Cache-basiert, Aktualisierung via `sync_metadata`) |
200
+ | `mantis://projects/{id}` | Kombinierte Projektansicht: Projektfelder + Benutzer + Versionen + Kategorien in einem Aufruf; Cache-first, List-Support zur Aufzählung aller verfügbaren Projekt-URIs |
198
201
  | `mantis://enums` | Gültige Werte für alle Issue-Enum-Felder: Severity, Priority, Status, Resolution, Reproducibility (Live-Abruf) |
199
202
 
200
203
  ## Verfügbare Prompts
package/README.md CHANGED
@@ -133,10 +133,11 @@ npm run build
133
133
 
134
134
  | Tool | Description |
135
135
  |---|---|
136
- | `list_projects` | List all accessible projects |
136
+ | `list_projects` | List all accessible projects; returns normalized project data (consistent with `sync_metadata` cache) |
137
137
  | `get_project_versions` | Get versions of a project; optional `obsolete` and `inherit` booleans to include obsolete or parent-inherited versions |
138
138
  | `get_project_categories` | Get categories of a project |
139
139
  | `get_project_users` | Get users of a project |
140
+ | `find_project_member` | Search project members by name, real name, or email (case-insensitive substring match); optional `query` and `limit` (default 10, max 100); cache-first |
140
141
 
141
142
  ### Semantic search *(optional)*
142
143
 
@@ -177,7 +178,8 @@ npm install sqlite-vec better-sqlite3
177
178
  | Tool | Description |
178
179
  |---|---|
179
180
  | `get_issue_fields` | Return all field names valid for the `select` parameter of `list_issues` |
180
- | `get_metadata` | Retrieve cached metadata (projects, users, versions, categories) |
181
+ | `get_metadata` | Retrieve a compact metadata summary: project/tag counts and per-project user/version/category counts; use `get_metadata_full` for complete arrays |
182
+ | `get_metadata_full` | Return the full raw metadata cache as minified JSON (all projects with complete fields, users/versions/categories per project, all tags) |
181
183
  | `sync_metadata` | Refresh the metadata cache |
182
184
  | `list_filters` | List saved filters |
183
185
  | `get_current_user` | Retrieve your own user profile |
@@ -194,7 +196,8 @@ MCP Resources are URI-addressable, read-only data that clients can fetch directl
194
196
  | Resource URI | Description |
195
197
  |---|---|
196
198
  | `mantis://me` | Profile of the authenticated API user (live fetch) |
197
- | `mantis://projects` | All accessible MantisBT projects (cache-backed, refreshed via `sync_metadata`) |
199
+ | `mantis://projects` | All accessible MantisBT projects as a compact list (cache-backed, refreshed via `sync_metadata`) |
200
+ | `mantis://projects/{id}` | Combined project view: project fields + users + versions + categories in one call; cache-first, list-support for enumerating all available project URIs |
198
201
  | `mantis://enums` | Valid values for all issue enum fields: severity, priority, status, resolution, reproducibility (live fetch) |
199
202
 
200
203
  ## Available prompts
package/dist/cache.js CHANGED
@@ -77,6 +77,9 @@ export class MetadataCache {
77
77
  return null;
78
78
  }
79
79
  }
80
+ getRemainingTtlSeconds(timestampMs) {
81
+ return Math.round(this.ttlSeconds - (Date.now() - timestampMs) / 1000);
82
+ }
80
83
  async saveIssueFields(fields) {
81
84
  await mkdir(this.cacheDir, { recursive: true });
82
85
  const file = { timestamp: Date.now(), fields };
package/dist/constants.js CHANGED
@@ -1,4 +1,9 @@
1
1
  // ---------------------------------------------------------------------------
2
+ // Category name normalization
3
+ // ---------------------------------------------------------------------------
4
+ /** Prefix MantisBT adds to category names inherited from [All Projects]. */
5
+ export const ALL_PROJECTS_PREFIX = '[All Projects] ';
6
+ // ---------------------------------------------------------------------------
2
7
  // Relationship type IDs
3
8
  // ---------------------------------------------------------------------------
4
9
  // Note: the MantisBT REST API only accepts numeric type IDs, not string names.
package/dist/index.js CHANGED
@@ -55,7 +55,7 @@ async function createMcpServer() {
55
55
  registerFileTools(server, client, startupConfig.uploadDir);
56
56
  registerRelationshipTools(server, client);
57
57
  registerMonitorTools(server, client);
58
- registerProjectTools(server, client);
58
+ registerProjectTools(server, client, cache);
59
59
  registerUserTools(server, client);
60
60
  registerFilterTools(server, client);
61
61
  registerConfigTools(server, client, cache);
@@ -1,10 +1,19 @@
1
+ import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
1
2
  import { fetchIssueEnums } from '../tools/config.js';
3
+ import { normalizeProject } from '../tools/metadata.js';
4
+ import { ALL_PROJECTS_PREFIX } from '../constants.js';
2
5
  function jsonResource(uri, data) {
3
6
  return {
4
- contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }],
7
+ contents: [{ uri: uri.href, mimeType: 'application/json', text: JSON.stringify(data) }],
5
8
  };
6
9
  }
7
10
  export function registerResources(server, client, cache) {
11
+ async function loadProjects() {
12
+ const cached = await cache.loadIfValid();
13
+ return cached?.projects
14
+ ?? (await client.get('projects')).projects?.map(normalizeProject)
15
+ ?? [];
16
+ }
8
17
  server.registerResource('current-user', 'mantis://me', {
9
18
  title: 'Current User',
10
19
  description: 'Profile of the user associated with the configured API key.',
@@ -14,12 +23,51 @@ export function registerResources(server, client, cache) {
14
23
  title: 'Projects',
15
24
  description: 'All MantisBT projects accessible to the current API user. Served from local cache when fresh; falls back to live fetch. Refresh via the sync_metadata tool.',
16
25
  mimeType: 'application/json',
17
- }, async (uri) => {
26
+ }, async (uri) => jsonResource(uri, await loadProjects()));
27
+ server.registerResource('project-detail', new ResourceTemplate('mantis://projects/{id}', {
28
+ list: async () => ({
29
+ resources: (await loadProjects()).map((p) => ({
30
+ uri: `mantis://projects/${p.id}`,
31
+ name: p.name,
32
+ })),
33
+ }),
34
+ }), {
35
+ title: 'Project Detail',
36
+ 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 the sync_metadata tool.',
37
+ mimeType: 'application/json',
38
+ }, async (uri, { id }) => {
39
+ const numId = Number(id);
18
40
  const cached = await cache.loadIfValid();
19
- const projects = cached?.projects
20
- ?? (await client.get('projects')).projects
21
- ?? [];
22
- return jsonResource(uri, projects);
41
+ let project;
42
+ let users;
43
+ let versions;
44
+ let categories;
45
+ if (cached) {
46
+ project = cached.projects.find((p) => p.id === numId);
47
+ const meta = cached.byProject[numId];
48
+ users = meta?.users ?? [];
49
+ versions = meta?.versions ?? [];
50
+ categories = meta?.categories ?? [];
51
+ }
52
+ else {
53
+ const [projectResult, usersResult, versionsResult] = await Promise.all([
54
+ client.get(`projects/${numId}`),
55
+ client.get(`projects/${numId}/users`),
56
+ client.get(`projects/${numId}/versions`, { obsolete: 1, inherit: 1 }),
57
+ ]);
58
+ const raw = projectResult.projects?.[0];
59
+ project = raw ? normalizeProject(raw) : undefined;
60
+ users = usersResult.users ?? [];
61
+ versions = versionsResult.versions ?? [];
62
+ categories = (raw?.categories ?? []).map((c) => ({
63
+ ...c,
64
+ name: c.name.startsWith(ALL_PROJECTS_PREFIX) ? c.name.slice(ALL_PROJECTS_PREFIX.length) : c.name,
65
+ }));
66
+ }
67
+ if (!project) {
68
+ throw new Error(`Project ${numId} not found`);
69
+ }
70
+ return jsonResource(uri, { ...project, users, versions, categories });
23
71
  });
24
72
  server.registerResource('issue-enums', 'mantis://enums', {
25
73
  title: 'Issue Enums',
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { getVersionHint } from '../version-hint.js';
3
+ import { ALL_PROJECTS_PREFIX } from '../constants.js';
3
4
  // Fields MantisBT strips from issue responses when the array is empty.
4
5
  // They are always valid 'select' values even if absent in a sample issue.
5
6
  const EMPTY_STRIPPED_FIELDS = [
@@ -83,10 +84,27 @@ async function collectTagsFromIssues(client, projects) {
83
84
  }
84
85
  return Array.from(tagMap.values()).sort((a, b) => a.id - b.id);
85
86
  }
87
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
88
+ function normalizeIdName(o) {
89
+ return { id: o.id, name: o.name, ...(o.label !== undefined && { label: o.label }) };
90
+ }
91
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
92
+ export function normalizeProject(p) {
93
+ return {
94
+ id: p.id,
95
+ name: p.name,
96
+ ...(p.description !== undefined && { description: p.description }),
97
+ ...(p.status !== undefined && { status: normalizeIdName(p.status) }),
98
+ ...(p.enabled !== undefined && { enabled: p.enabled }),
99
+ ...(p.view_state !== undefined && { view_state: normalizeIdName(p.view_state) }),
100
+ ...(p.access_level !== undefined && { access_level: { id: p.access_level.id, label: p.access_level.label } }),
101
+ ...(p.subprojects !== undefined && { subprojects: p.subprojects.map(normalizeProject) }),
102
+ };
103
+ }
86
104
  async function fetchAndCacheMetadata(client, cache) {
87
105
  // Fetch all projects
88
106
  const projectResult = await client.get('projects');
89
- const projects = projectResult.projects ?? [];
107
+ const projects = (projectResult.projects ?? []).map(normalizeProject);
90
108
  const byProject = {};
91
109
  // For each project, fetch users, versions, categories in parallel
92
110
  await Promise.all(projects.map(async (project) => {
@@ -102,7 +120,6 @@ async function fetchAndCacheMetadata(client, cache) {
102
120
  const versions = versionsResult.status === 'fulfilled'
103
121
  ? (versionsResult.value.versions ?? [])
104
122
  : [];
105
- const ALL_PROJECTS_PREFIX = '[All Projects] ';
106
123
  const rawCategories = projectDetailResult.status === 'fulfilled'
107
124
  ? (projectDetailResult.value.projects?.[0]?.categories ?? [])
108
125
  : [];
@@ -177,10 +194,52 @@ Use this tool to refresh stale data.`,
177
194
  // ---------------------------------------------------------------------------
178
195
  server.registerTool('get_metadata', {
179
196
  title: 'Get Cached Metadata',
180
- description: `Return cached MantisBT metadata (projects, users, versions, categories).
197
+ description: `Return a compact summary of cached MantisBT metadata: project count, tag count, and per-project counts of users, versions, and categories.
198
+
199
+ If the cache does not exist or has expired (default TTL: 24 hours), it will automatically sync first.
200
+ Use sync_metadata to force a refresh. For full lists use: list_projects (projects), get_project_users / get_project_versions / get_project_categories (per-project data), list_tags (tags).`,
201
+ inputSchema: z.object({}),
202
+ annotations: {
203
+ readOnlyHint: true,
204
+ destructiveHint: false,
205
+ idempotentHint: true,
206
+ },
207
+ }, async () => {
208
+ try {
209
+ const data = await cache.loadIfValid() ?? await fetchAndCacheMetadata(client, cache);
210
+ const summary = {
211
+ projects: data.projects.length,
212
+ tags: data.tags.length,
213
+ byProject: Object.fromEntries(data.projects.map((p) => {
214
+ const meta = data.byProject[p.id];
215
+ return [String(p.id), {
216
+ name: p.name,
217
+ users: meta?.users.length ?? 0,
218
+ versions: meta?.versions.length ?? 0,
219
+ categories: meta?.categories.length ?? 0,
220
+ }];
221
+ })),
222
+ cached_at: new Date(data.timestamp).toISOString(),
223
+ ttl_seconds: cache.getRemainingTtlSeconds(data.timestamp),
224
+ };
225
+ return {
226
+ content: [{ type: 'text', text: JSON.stringify(summary, null, 2) }],
227
+ };
228
+ }
229
+ catch (error) {
230
+ const msg = error instanceof Error ? error.message : String(error);
231
+ return { content: [{ type: 'text', text: errorText(msg) }], isError: true };
232
+ }
233
+ });
234
+ // ---------------------------------------------------------------------------
235
+ // get_metadata_full
236
+ // ---------------------------------------------------------------------------
237
+ server.registerTool('get_metadata_full', {
238
+ title: 'Get Full Cached Metadata',
239
+ description: `Return the complete raw MantisBT metadata cache: all projects with full fields, and per-project lists of users, versions, categories, plus all tags.
181
240
 
182
241
  If the cache does not exist or has expired (default TTL: 24 hours), it will automatically sync first.
183
- Use sync_metadata to force a refresh.`,
242
+ Use sync_metadata to force a refresh. For a lightweight overview use get_metadata instead.`,
184
243
  inputSchema: z.object({}),
185
244
  annotations: {
186
245
  readOnlyHint: true,
@@ -191,7 +250,7 @@ Use sync_metadata to force a refresh.`,
191
250
  try {
192
251
  const data = await cache.loadIfValid() ?? await fetchAndCacheMetadata(client, cache);
193
252
  return {
194
- content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
253
+ content: [{ type: 'text', text: JSON.stringify(data) }],
195
254
  };
196
255
  }
197
256
  catch (error) {
@@ -1,13 +1,14 @@
1
1
  import { z } from 'zod';
2
2
  import { getVersionHint } from '../version-hint.js';
3
+ import { ALL_PROJECTS_PREFIX } from '../constants.js';
4
+ import { normalizeProject } from './metadata.js';
3
5
  function errorText(msg) {
4
6
  const vh = getVersionHint();
5
7
  vh?.triggerLatestVersionFetch();
6
8
  const hint = vh?.getUpdateHint();
7
9
  return hint ? `Error: ${msg}\n\n${hint}` : `Error: ${msg}`;
8
10
  }
9
- const ALL_PROJECTS_PREFIX = '[All Projects] ';
10
- export function registerProjectTools(server, client) {
11
+ export function registerProjectTools(server, client, cache) {
11
12
  // ---------------------------------------------------------------------------
12
13
  // list_projects
13
14
  // ---------------------------------------------------------------------------
@@ -23,8 +24,9 @@ export function registerProjectTools(server, client) {
23
24
  }, async () => {
24
25
  try {
25
26
  const result = await client.get('projects');
27
+ const projects = (result.projects ?? []).map(normalizeProject);
26
28
  return {
27
- content: [{ type: 'text', text: JSON.stringify(result.projects ?? result, null, 2) }],
29
+ content: [{ type: 'text', text: JSON.stringify(projects, null, 2) }],
28
30
  };
29
31
  }
30
32
  catch (error) {
@@ -131,4 +133,50 @@ This tool strips that prefix so the returned names can be used directly when cre
131
133
  return { content: [{ type: 'text', text: errorText(msg) }], isError: true };
132
134
  }
133
135
  });
136
+ // ---------------------------------------------------------------------------
137
+ // find_project_member
138
+ // ---------------------------------------------------------------------------
139
+ server.registerTool('find_project_member', {
140
+ title: 'Find Project Member',
141
+ description: `Search for users with access to a MantisBT project by name, display name, or email.
142
+
143
+ Returns up to \`limit\` matching users (default: 10, max: 100). Matching is case-insensitive substring search across \`name\`, \`real_name\`, and \`email\` fields. Omit \`query\` to list the first \`limit\` users.
144
+
145
+ Data is served from the local metadata cache when fresh; falls back to a live API call otherwise.`,
146
+ inputSchema: z.object({
147
+ project_id: z.coerce.number().int().positive().describe('Numeric project ID'),
148
+ query: z.string().optional().describe('Case-insensitive substring to match against name, real_name, or email'),
149
+ limit: z.coerce.number().int().min(1).max(100).default(10).describe('Maximum number of results to return (default: 10, max: 100)'),
150
+ }),
151
+ annotations: {
152
+ readOnlyHint: true,
153
+ destructiveHint: false,
154
+ idempotentHint: true,
155
+ },
156
+ }, async ({ project_id, query, limit }) => {
157
+ try {
158
+ let users;
159
+ const cached = cache ? await cache.loadIfValid() : null;
160
+ if (cached?.byProject[project_id]) {
161
+ users = cached.byProject[project_id].users;
162
+ }
163
+ else {
164
+ const result = await client.get(`projects/${project_id}/users`);
165
+ users = result.users ?? [];
166
+ }
167
+ if (query) {
168
+ const q = query.toLowerCase();
169
+ users = users.filter((u) => u.name.toLowerCase().includes(q) ||
170
+ (u.real_name?.toLowerCase().includes(q) ?? false) ||
171
+ (u.email?.toLowerCase().includes(q) ?? false));
172
+ }
173
+ return {
174
+ content: [{ type: 'text', text: JSON.stringify(users.slice(0, limit), null, 2) }],
175
+ };
176
+ }
177
+ catch (error) {
178
+ const msg = error instanceof Error ? error.message : String(error);
179
+ return { content: [{ type: 'text', text: errorText(msg) }], isError: true };
180
+ }
181
+ });
134
182
  }
@@ -46,6 +46,10 @@ Tool-orientierte Rezepte für den MantisBT MCP Server — jedes Rezept zeigt gen
46
46
  - [Suche mit Felderweiterung](#suche-mit-felderweiterung)
47
47
  - [Projekte & Kategorien](#projekte--kategorien)
48
48
  - [Projektkategorien auflisten](#projektkategorien-auflisten)
49
+ - [Projektmitglied suchen](#projektmitglied-suchen)
50
+ - [Metadaten](#metadaten)
51
+ - [Metadaten-Zusammenfassung abrufen](#metadaten-zusammenfassung-abrufen)
52
+ - [Vollständigen Metadaten-Cache abrufen](#vollständigen-metadaten-cache-abrufen)
49
53
  - [Version & Diagnose](#version--diagnose)
50
54
  - [MCP-Server-Version abrufen](#mcp-server-version-abrufen)
51
55
  - [MantisBT-Version abrufen](#mantisbt-version-abrufen)
@@ -53,6 +57,7 @@ Tool-orientierte Rezepte für den MantisBT MCP Server — jedes Rezept zeigt gen
53
57
  - [Ressourcen](#ressourcen)
54
58
  - [Eigenes Benutzerprofil lesen](#eigenes-benutzerprofil-lesen)
55
59
  - [Alle Projekte lesen](#alle-projekte-lesen)
60
+ - [Einzelnes Projekt mit allen Details lesen](#einzelnes-projekt-mit-allen-details-lesen)
56
61
  - [Issue-Enum-Werte lesen](#issue-enum-werte-lesen)
57
62
  - [Prompts](#prompts)
58
63
  - [Bug-Report erstellen](#bug-report-erstellen)
@@ -1079,7 +1084,7 @@ Entfernt einen Tag von einem Issue. Erfordert die numerische Tag-ID.
1079
1084
  "Tag #14 successfully removed from issue #1042."
1080
1085
  ```
1081
1086
 
1082
- > **Hinweis:** `detach_tag` erfordert eine numerische ID, keinen Tag-Namen. Es gibt keine Suche per Name — die ID muss immer zuerst über `get_issue` oder `get_metadata` abgerufen werden.
1087
+ > **Hinweis:** `detach_tag` erfordert eine numerische ID, keinen Tag-Namen. Es gibt keine Suche per Name — die ID muss immer zuerst über `get_issue` oder `list_tags` abgerufen werden.
1083
1088
 
1084
1089
  ---
1085
1090
 
@@ -1338,6 +1343,123 @@ Gibt alle verfügbaren Kategorien eines MantisBT-Projekts zurück. Die zurückge
1338
1343
 
1339
1344
  ---
1340
1345
 
1346
+ ### Projektmitglied suchen
1347
+
1348
+ Sucht Projektmitglieder nach Name, Realname oder E-Mail. Die Suche ist Groß-/Kleinschreibungsunabhängig und findet Teilstrings. Ergebnisse werden bevorzugt aus dem Metadaten-Cache geliefert; bei leerem Cache wird die live API verwendet.
1349
+
1350
+ **Tool:** `find_project_member`
1351
+
1352
+ **Parameter:**
1353
+ - `project_id` — numerische Projekt-ID
1354
+ - `query` — _(optional)_ Suchbegriff für `name`, `real_name` oder `email`
1355
+ - `limit` — _(optional)_ maximale Trefferanzahl, Standard 10, max. 100
1356
+
1357
+ **Request:**
1358
+
1359
+ ```json
1360
+ {
1361
+ "project_id": 3,
1362
+ "query": "smith",
1363
+ "limit": 5
1364
+ }
1365
+ ```
1366
+
1367
+ **Response:**
1368
+
1369
+ ```json
1370
+ [
1371
+ { "id": 4, "name": "jsmith", "real_name": "John Smith", "email": "jsmith@example.com", "access_level": { "id": 55, "name": "developer" } },
1372
+ { "id": 11, "name": "asmith", "real_name": "Alice Smith", "email": "asmith@example.com", "access_level": { "id": 40, "name": "reporter" } }
1373
+ ]
1374
+ ```
1375
+
1376
+ > **Tipp:** `query` weglassen, um alle Mitglieder des Projekts aufzulisten (bis zu `limit`).
1377
+
1378
+ ---
1379
+
1380
+ ## Metadaten
1381
+
1382
+ ### Metadaten-Zusammenfassung abrufen
1383
+
1384
+ Gibt eine kompakte Übersicht aller gecachten Metadaten zurück: Gesamtzahl der Projekte und Tags sowie die Anzahl von Benutzern, Versionen und Kategorien je Projekt. Nützlich für einen schnellen Überblick, ohne große Arrays zu übertragen.
1385
+
1386
+ **Tool:** `get_metadata`
1387
+
1388
+ **Parameter:** _(keine)_
1389
+
1390
+ **Request:**
1391
+
1392
+ ```json
1393
+ {}
1394
+ ```
1395
+
1396
+ **Response:**
1397
+
1398
+ ```json
1399
+ {
1400
+ "projects": 24,
1401
+ "tags": 15,
1402
+ "byProject": {
1403
+ "3": { "name": "Webshop", "users": 8, "versions": 12, "categories": 4 },
1404
+ "5": { "name": "Backend API", "users": 5, "versions": 7, "categories": 3 }
1405
+ },
1406
+ "cached_at": "2026-03-27T09:00:00.000Z",
1407
+ "ttl_seconds": 82800
1408
+ }
1409
+ ```
1410
+
1411
+ > **Hinweis:** Für vollständige Listen `list_projects`, `get_project_users`, `get_project_versions`, `get_project_categories`, `list_tags` oder `get_metadata_full` verwenden.
1412
+
1413
+ ---
1414
+
1415
+ ### Vollständigen Metadaten-Cache abrufen
1416
+
1417
+ Gibt den vollständigen rohen Metadaten-Cache als minifiziertes JSON zurück. Enthält alle Projekte mit vollständigen Feldern sowie Benutzer, Versionen und Kategorien je Projekt und alle Tags. Geeignet, wenn alle Daten in einem einzigen Aufruf benötigt werden.
1418
+
1419
+ **Tool:** `get_metadata_full`
1420
+
1421
+ **Parameter:** _(keine)_
1422
+
1423
+ **Request:**
1424
+
1425
+ ```json
1426
+ {}
1427
+ ```
1428
+
1429
+ **Response:**
1430
+
1431
+ ```json
1432
+ {
1433
+ "projects": [
1434
+ {
1435
+ "id": 3,
1436
+ "name": "Webshop",
1437
+ "status": { "id": 10, "name": "development" },
1438
+ "enabled": true,
1439
+ "users": [
1440
+ { "id": 4, "name": "jsmith", "real_name": "John Smith", "email": "jsmith@example.com", "access_level": { "id": 55, "name": "developer" } }
1441
+ ],
1442
+ "versions": [
1443
+ { "id": 21, "name": "2.4.0", "released": false, "obsolete": false }
1444
+ ],
1445
+ "categories": [
1446
+ { "id": 1, "name": "General" },
1447
+ { "id": 2, "name": "UI" }
1448
+ ]
1449
+ }
1450
+ ],
1451
+ "tags": [
1452
+ { "id": 1, "name": "regression" },
1453
+ { "id": 2, "name": "hotfix" }
1454
+ ],
1455
+ "cached_at": "2026-03-27T09:00:00.000Z"
1456
+ }
1457
+ ```
1458
+
1459
+ > **Tipp:** `get_metadata` liefert dieselben Daten als kompakte Zusammenfassung (nur Zählwerte). `get_metadata_full` verwenden, wenn die eigentlichen Arrays benötigt werden.
1460
+
1461
+ ---
1462
+
1341
1463
  ## Version & Diagnose
1342
1464
 
1343
1465
  ### MCP-Server-Version abrufen
@@ -1476,6 +1598,41 @@ Gibt alle MantisBT-Projekte zurück, auf die der konfigurierte API-Key Zugriff h
1476
1598
 
1477
1599
  ---
1478
1600
 
1601
+ ### Einzelnes Projekt mit allen Details lesen
1602
+
1603
+ Gibt eine kombinierte Ansicht eines einzelnen Projekts zurück: Projektfelder sowie alle Mitglieder, Versionen und Kategorien in einem Aufruf. Cache-first (MetadataCache); bei leerem Cache werden drei parallele API-Aufrufe durchgeführt. Clients mit Resource-List-Unterstützung können alle verfügbaren Projekt-URIs aufzählen.
1604
+
1605
+ **Ressource-URI:** `mantis://projects/{id}` (`{id}` durch die numerische Projekt-ID ersetzen)
1606
+
1607
+ **Abrufverhalten:** Cache-first (MetadataCache, Standard-TTL 24 h). Fällt auf Live-API-Aufrufe zurück, wenn der Cache leer ist.
1608
+
1609
+ **Beispiel-URI:** `mantis://projects/42`
1610
+
1611
+ **Response:**
1612
+
1613
+ ```json
1614
+ {
1615
+ "id": 3,
1616
+ "name": "Webshop",
1617
+ "status": { "id": 10, "name": "development" },
1618
+ "enabled": true,
1619
+ "users": [
1620
+ { "id": 4, "name": "jsmith", "real_name": "John Smith", "email": "jsmith@example.com", "access_level": { "id": 55, "name": "developer" } }
1621
+ ],
1622
+ "versions": [
1623
+ { "id": 21, "name": "2.4.0", "released": false, "obsolete": false }
1624
+ ],
1625
+ "categories": [
1626
+ { "id": 1, "name": "General" },
1627
+ { "id": 2, "name": "UI" }
1628
+ ]
1629
+ }
1630
+ ```
1631
+
1632
+ > **Tipp:** `mantis://projects` für eine kompakte Liste aller Projekte verwenden, anschließend Detaildaten einzelner Projekte über `mantis://projects/{id}` abrufen.
1633
+
1634
+ ---
1635
+
1479
1636
  ### Issue-Enum-Werte lesen
1480
1637
 
1481
1638
  Gibt gültige ID/Name-Paare für alle Issue-Enum-Felder zurück (Severity, Priority, Status, Resolution, Reproducibility). Vor `create_issue` oder `update_issue` verwenden, um kanonische englische Namen nachzuschlagen.