list_issues
Retrieve and filter MantisBT bug tracker issues with pagination, field selection, and client-side filtering for assignee, reporter, and status.
Instructions
List MantisBT issues with optional filtering. Returns a paginated list of issues. Use the "select" parameter to limit returned fields and reduce response size significantly.
Note: "assigned_to", "reporter_id", and "status" filters are applied client-side (the MantisBT REST API does not reliably support these as server-side filters). When any of these filters are active the tool automatically fetches multiple pages internally until enough matching results are found (up to 500 issues scanned). The "page" and "page_size" parameters refer to the resulting filtered list.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| project_id | No | Filter by project ID | |
| page | No | Page number (default: 1) | |
| page_size | No | Issues per page (default: 50, max: 50) | |
| assigned_to | No | Filter by handler/assignee user ID | |
| reporter_id | No | Filter by reporter user ID | |
| filter_id | No | Use a saved MantisBT filter ID | |
| sort | No | Sort field (e.g. "last_updated", "id") | |
| direction | No | Sort direction | |
| select | No | Comma-separated list of fields to include in the response (server-side projection). Significantly reduces response size. Example: "id,summary,status,priority,handler,updated_at" | |
| status | No | Filter issues by status name (e.g. "new", "feedback", "acknowledged", "confirmed", "assigned", "resolved", "closed") or use "open" as shorthand for all statuses with id < 80 (i.e. not yet resolved or closed). Applied client-side after fetching — when combined with pagination, a page may contain fewer results than page_size. |
Implementation Reference
- src/tools/issues.ts:77-149 (handler)The async handler function for 'list_issues' which handles both direct API calls and client-side filtering (when assigned_to, reporter_id, or status filters are used).
async ({ project_id, page, page_size, assigned_to, reporter_id, filter_id, sort, direction, select, status }) => { try { const baseParams: Record<string, string | number | boolean | undefined> = { project_id, assigned_to, reporter_id, filter_id, sort, direction, select, }; const needsClientFilter = status !== undefined || assigned_to !== undefined || reporter_id !== undefined; if (!needsClientFilter) { // No client-side filtering — single API call, pass pagination as-is const result = await client.get<MantisPaginatedIssues>('issues', { ...baseParams, page, page_size }); return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }], }; } // Client-side filtering active: scan multiple API pages until we have // enough matching results for the requested logical page. const API_PAGE_SIZE = 50; // always fetch max to minimise round-trips const MAX_API_PAGES = 10; // hard cap: scan at most 500 issues const neededTotal = page * page_size; // need this many matches to serve page N const matching: MantisIssue[] = []; let serverPage = 1; let hasMore = true; const statusLower = status?.toLowerCase(); while (matching.length < neededTotal && serverPage <= MAX_API_PAGES && hasMore) { const batch = await client.get<MantisPaginatedIssues>('issues', { ...baseParams, page: serverPage, page_size: API_PAGE_SIZE, }); const issues = batch.issues ?? []; hasMore = issues.length === API_PAGE_SIZE; for (const issue of issues) { if (statusLower) { if (!issue.status) continue; if (statusLower === 'open') { if ((issue.status.id ?? 0) >= MANTIS_RESOLVED_STATUS_ID) continue; } else if (issue.status.name?.toLowerCase() !== statusLower) { continue; } } if (assigned_to !== undefined && issue.handler?.id !== assigned_to) continue; if (reporter_id !== undefined && issue.reporter?.id !== reporter_id) continue; matching.push(issue); } serverPage++; } const start = (page - 1) * page_size; return { content: [{ type: 'text', text: JSON.stringify({ issues: matching.slice(start, start + page_size) }, null, 2), }], }; } catch (error) { const msg = error instanceof Error ? error.message : String(error); return { content: [{ type: 'text', text: errorText(msg) }], isError: true }; } } - src/tools/issues.ts:59-70 (schema)The Zod schema defining the input parameters for 'list_issues', including pagination and filtering options.
inputSchema: z.object({ project_id: z.coerce.number().int().positive().optional().describe('Filter by project ID'), page: z.coerce.number().int().positive().default(1).describe('Page number (default: 1)'), page_size: z.coerce.number().int().min(1).max(50).default(50).describe('Issues per page (default: 50, max: 50)'), assigned_to: z.coerce.number().int().positive().optional().describe('Filter by handler/assignee user ID'), reporter_id: z.coerce.number().int().positive().optional().describe('Filter by reporter user ID'), filter_id: z.coerce.number().int().positive().optional().describe('Use a saved MantisBT filter ID'), sort: z.string().optional().describe('Sort field (e.g. "last_updated", "id")'), direction: z.enum(['ASC', 'DESC']).optional().describe('Sort direction'), select: z.string().optional().describe('Comma-separated list of fields to include in the response (server-side projection). Significantly reduces response size. Example: "id,summary,status,priority,handler,updated_at"'), status: z.string().optional().describe('Filter issues by status name (e.g. "new", "feedback", "acknowledged", "confirmed", "assigned", "resolved", "closed") or use "open" as shorthand for all statuses with id < 80 (i.e. not yet resolved or closed). Applied client-side after fetching — when combined with pagination, a page may contain fewer results than page_size.'), }), - src/tools/issues.ts:54-76 (registration)Tool registration block for 'list_issues' in 'src/tools/issues.ts'.
server.registerTool( 'list_issues', { title: 'List Issues', description: 'List MantisBT issues with optional filtering. Returns a paginated list of issues. Use the "select" parameter to limit returned fields and reduce response size significantly.\n\nNote: "assigned_to", "reporter_id", and "status" filters are applied client-side (the MantisBT REST API does not reliably support these as server-side filters). When any of these filters are active the tool automatically fetches multiple pages internally until enough matching results are found (up to 500 issues scanned). The "page" and "page_size" parameters refer to the resulting filtered list.', inputSchema: z.object({ project_id: z.coerce.number().int().positive().optional().describe('Filter by project ID'), page: z.coerce.number().int().positive().default(1).describe('Page number (default: 1)'), page_size: z.coerce.number().int().min(1).max(50).default(50).describe('Issues per page (default: 50, max: 50)'), assigned_to: z.coerce.number().int().positive().optional().describe('Filter by handler/assignee user ID'), reporter_id: z.coerce.number().int().positive().optional().describe('Filter by reporter user ID'), filter_id: z.coerce.number().int().positive().optional().describe('Use a saved MantisBT filter ID'), sort: z.string().optional().describe('Sort field (e.g. "last_updated", "id")'), direction: z.enum(['ASC', 'DESC']).optional().describe('Sort direction'), select: z.string().optional().describe('Comma-separated list of fields to include in the response (server-side projection). Significantly reduces response size. Example: "id,summary,status,priority,handler,updated_at"'), status: z.string().optional().describe('Filter issues by status name (e.g. "new", "feedback", "acknowledged", "confirmed", "assigned", "resolved", "closed") or use "open" as shorthand for all statuses with id < 80 (i.e. not yet resolved or closed). Applied client-side after fetching — when combined with pagination, a page may contain fewer results than page_size.'), }), annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, }, },