Skip to main content
Glama

JIRA MCP Server

search-issues.use-case.ts6 kB
/** * Search Issues Use Case * * Business logic for searching JIRA issues with complex JQL query building * and validation capabilities */ import { logger } from "@core/logging"; import { JiraApiError } from "@features/jira/client/errors"; import { z } from "zod"; import type { Issue, SearchIssuesOptions } from "../models"; import type { IssueSearchRepository } from "../repositories"; /** * Base schema for search parameters (without refinement) * Used for MCP tool parameter definition */ export const searchJiraIssuesBaseSchema = z.object({ // Advanced JQL option jql: z.string().min(1).optional(), // Helper parameters (ignored if jql provided) assignedToMe: z.boolean().optional(), project: z.string().optional(), status: z.union([z.string(), z.array(z.string())]).optional(), text: z.string().optional(), // Common options maxResults: z.number().min(1).max(50).default(25), fields: z.array(z.string()).optional(), }); /** * Schema for search JIRA issues with hybrid JQL + helper parameters */ export const searchJiraIssuesSchema = searchJiraIssuesBaseSchema.refine( (data) => data.jql || data.assignedToMe || data.project || data.status || data.text, { message: "Either 'jql' parameter or at least one helper parameter must be provided", }, ); /** * Type for search parameters */ export type SearchJiraIssuesParams = z.infer<typeof searchJiraIssuesSchema>; /** * Build JQL query from helper parameters * @param params - Search parameters * @returns JQL query string */ export function buildJQLFromHelpers(params: SearchJiraIssuesParams): string { // If JQL is provided directly, use it if (params.jql) { return params.jql; } const conditions: string[] = []; if (params.assignedToMe) { conditions.push("assignee = currentUser()"); } if (params.project) { conditions.push(`project = "${params.project}"`); } if (params.status) { const statuses = Array.isArray(params.status) ? params.status : [params.status]; conditions.push(`status IN (${statuses.map((s) => `"${s}"`).join(", ")})`); } if (params.text) { const escapedText = params.text.replace(/"/g, '\\"'); conditions.push( `(summary ~ "${escapedText}" OR description ~ "${escapedText}")`, ); } return `${conditions.join(" AND ")} ORDER BY updated DESC`; } /** * Request parameters for search issues use case */ export interface SearchIssuesUseCaseRequest { jql?: string; text?: string; project?: string; status?: string | string[]; assignedToMe?: boolean; maxResults?: number; fields?: string[]; } /** * Interface for search issues use case */ export interface SearchIssuesUseCase { /** * Execute the search issues use case * * @param request - Search parameters * @returns List of JIRA issues matching the search criteria */ execute(request: SearchIssuesUseCaseRequest): Promise<Issue[]>; } /** * Implementation of the search issues use case */ export class SearchIssuesUseCaseImpl implements SearchIssuesUseCase { /** * Create a new SearchIssuesUseCase implementation * * @param issueSearchRepository - Repository for searching issues */ constructor(private readonly issueSearchRepository: IssueSearchRepository) {} /** * Execute the search issues use case * * @param request - Search parameters * @returns List of JIRA issues matching the search criteria */ public async execute(request: SearchIssuesUseCaseRequest): Promise<Issue[]> { try { logger.debug("Executing search issues use case", { prefix: "JIRA:SearchIssuesUseCase", request, }); // Build JQL query from search parameters const jqlQuery = this.buildJQLQuery(request); // Prepare search options const searchOptions: SearchIssuesOptions = { jql: jqlQuery, fields: request.fields || [ "summary", "status", "priority", "assignee", "created", "updated", ], maxResults: request.maxResults || 25, startAt: 0, }; // Search for issues using repository const searchResult = await this.issueSearchRepository.searchIssues(searchOptions); logger.debug(`Found ${searchResult.issues.length} issues`, { prefix: "JIRA:SearchIssuesUseCase", total: searchResult.total, }); return searchResult.issues; } catch (error) { logger.error("Failed to search issues", { prefix: "JIRA:SearchIssuesUseCase", error: error instanceof Error ? error.message : String(error), }); // Rethrow with better context if needed if (error instanceof Error) { throw JiraApiError.withStatusCode( `Failed to search issues: ${error.message}`, 400, ); } throw error; } } /** * Build JQL query from search parameters * * @param params - Search parameters * @returns JQL query string */ private buildJQLQuery(params: SearchIssuesUseCaseRequest): string { // If JQL is provided directly, use it if (params.jql) { return params.jql; } // Build JQL from helper parameters const conditions: string[] = []; if (params.assignedToMe) { conditions.push("assignee = currentUser()"); } if (params.project) { conditions.push(`project = "${params.project}"`); } if (params.status) { if (Array.isArray(params.status)) { const statusList = params.status.map((s) => `"${s}"`).join(", "); conditions.push(`status IN (${statusList})`); } else { conditions.push(`status = "${params.status}"`); } } if (params.text) { conditions.push(`text ~ "${params.text}"`); } // If no conditions, search for all issues if (conditions.length === 0) { return "ORDER BY updated DESC"; } return `${conditions.join(" AND ")} ORDER BY updated DESC`; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Dsazz/mcp-jira'

If you have feedback or need assistance with the MCP directory API, please join our Discord server