search_jira_issues
Search Jira issues using JQL queries with pagination and customizable field selection to find specific tickets based on criteria like project, status, or assignee.
Instructions
Search issues using JQL with pagination and field selection.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| jql | Yes | JQL query (e.g., project=PROJ AND status="In Progress") | |
| startAt | No | Pagination start index (default 0) | |
| maxResults | No | Page size (1-100, default 50) | |
| fields | No | Comma-separated fields to return (default: key,summary,status,assignee,priority,issuetype,updated) |
Implementation Reference
- src/server.ts:252-272 (handler)The handler function executes the tool logic: constructs JQL search body, POSTs to Jira /search API, maps response issues to structured content with summaries, URLs, etc., handles pagination, errors.async (args: { jql: string; startAt?: number; maxResults?: number; fields?: string }) => { try { const body: any = { jql: args.jql, startAt: args.startAt ?? 0, maxResults: args.maxResults ?? 50, fields: (args.fields ? args.fields.split(",").map(s => s.trim()) : ["key","summary","status","assignee","priority","issuetype","updated"]).filter(Boolean) }; const url = `${JIRA_URL}/rest/api/3/search`; const response = await fetch(url, { method: "POST", headers: getJiraHeaders(), body: JSON.stringify(body) }); if (!response.ok) { const errorText = await response.text(); return { content: [{ type: "text", text: `Failed to search issues: ${response.status} ${response.statusText}\n${errorText}` }], isError: true }; } const data = await response.json() as any; const items = (data.issues || []).map((it: any) => ({ key: it.key, summary: it.fields?.summary, status: it.fields?.status?.name, assignee: it.fields?.assignee?.displayName, priority: it.fields?.priority?.name, type: it.fields?.issuetype?.name, updated: it.fields?.updated, url: `${JIRA_URL}/browse/${it.key}` })); return { content: [{ type: "text", text: `Found ${data.total ?? items.length} issues (showing ${items.length}).` }], structuredContent: { total: data.total ?? items.length, startAt: data.startAt ?? 0, maxResults: data.maxResults ?? items.length, issues: items, raw: data } }; } catch (error) { return { content: [{ type: "text", text: `Error searching issues: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } }
- src/server.ts:242-251 (schema)Input schema using Zod for validation of JQL query, pagination parameters (startAt, maxResults), and optional fields.{ title: "Search Jira Issues (JQL)", description: "Search issues using JQL with pagination and field selection.", inputSchema: { jql: z.string().describe("JQL query (e.g., project=PROJ AND status=\"In Progress\")"), startAt: z.number().int().min(0).optional().describe("Pagination start index (default 0)"), maxResults: z.number().int().min(1).max(100).optional().describe("Page size (1-100, default 50)"), fields: z.string().optional().describe("Comma-separated fields to return (default: key,summary,status,assignee,priority,issuetype,updated)") }, },
- src/server.ts:240-273 (registration)Registration of the 'search_jira_issues' tool with McpServer, including name, schema, and handler reference.mcp.registerTool( "search_jira_issues", { title: "Search Jira Issues (JQL)", description: "Search issues using JQL with pagination and field selection.", inputSchema: { jql: z.string().describe("JQL query (e.g., project=PROJ AND status=\"In Progress\")"), startAt: z.number().int().min(0).optional().describe("Pagination start index (default 0)"), maxResults: z.number().int().min(1).max(100).optional().describe("Page size (1-100, default 50)"), fields: z.string().optional().describe("Comma-separated fields to return (default: key,summary,status,assignee,priority,issuetype,updated)") }, }, async (args: { jql: string; startAt?: number; maxResults?: number; fields?: string }) => { try { const body: any = { jql: args.jql, startAt: args.startAt ?? 0, maxResults: args.maxResults ?? 50, fields: (args.fields ? args.fields.split(",").map(s => s.trim()) : ["key","summary","status","assignee","priority","issuetype","updated"]).filter(Boolean) }; const url = `${JIRA_URL}/rest/api/3/search`; const response = await fetch(url, { method: "POST", headers: getJiraHeaders(), body: JSON.stringify(body) }); if (!response.ok) { const errorText = await response.text(); return { content: [{ type: "text", text: `Failed to search issues: ${response.status} ${response.statusText}\n${errorText}` }], isError: true }; } const data = await response.json() as any; const items = (data.issues || []).map((it: any) => ({ key: it.key, summary: it.fields?.summary, status: it.fields?.status?.name, assignee: it.fields?.assignee?.displayName, priority: it.fields?.priority?.name, type: it.fields?.issuetype?.name, updated: it.fields?.updated, url: `${JIRA_URL}/browse/${it.key}` })); return { content: [{ type: "text", text: `Found ${data.total ?? items.length} issues (showing ${items.length}).` }], structuredContent: { total: data.total ?? items.length, startAt: data.startAt ?? 0, maxResults: data.maxResults ?? items.length, issues: items, raw: data } }; } catch (error) { return { content: [{ type: "text", text: `Error searching issues: ${error instanceof Error ? error.message : String(error)}` }], isError: true }; } } );
- src/server.ts:37-44 (helper)Shared helper function that creates authentication headers using Jira email and API token, used by the search handler and other tools.function getJiraHeaders(): Record<string, string> { const auth = Buffer.from(`${JIRA_EMAIL}:${JIRA_API_TOKEN}`).toString('base64'); return { 'Authorization': `Basic ${auth}`, 'Accept': 'application/json', 'Content-Type': 'application/json', }; }