Skip to main content
Glama
issues.ts16.5 kB
import z from 'zod'; import { Tool } from '../tool'; import { apiKey, jiraProjectUrl } from '../../utils/utils'; import { Issue } from './issues.types'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { formatIssue } from './utils'; import { createIssueSchema } from '../schemas/createIssue'; import { searchIssuesSchema } from '../schemas/searchIssue'; import { assignIssueSchema, unassignIssueSchema, userResponseSchema } from '../schemas/assignIssue'; import { editIssueSchema, updatePayload } from '../schemas/editIssue'; import { transitionIssueSchema, transitionsSchema } from '../schemas/transitionIssue'; import { archiveIssueSchema, archiveResponseSchema } from '../schemas/archiveIssue'; const issueKeySchema = z.object({ key: z.string().describe('The key of the issue (e.g. ABC-1)'), }); const getIssueByKey: Tool = { schema: { name: 'get_issue_by_key', description: 'Get an issue by key', inputSchema: zodToJsonSchema(issueKeySchema), }, handle: async (params) => { let validParams; const result = issueKeySchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], }; } else { validParams = result.data; } const issueUrl = `${process.env.JIRA_PROJECT_URL}/issue/${validParams.key}`; const response = await fetch(issueUrl, { headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, }); if (!response.ok) { return { content: [ { type: 'text', text: `Error retrieving issue. Error: ${JSON.stringify(await response.json())}`, }, ], isError: true, }; } const data: Issue = await response.json(); const formattedIssue = formatIssue(data); const issueText = `Issue ${validParams.key}:\n\n${formattedIssue}`; return { content: [ { type: 'text', text: issueText, }, ], }; }, }; const searchIssues: Tool = { schema: { name: 'search_issues', description: 'Search for issues using JQL', inputSchema: zodToJsonSchema(searchIssuesSchema), }, handle: async (params) => { let validParams; const result = searchIssuesSchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], }; } else { validParams = result.data; } const issueUrl = `${process.env.JIRA_PROJECT_URL}/search/jql`; const response = await fetch(issueUrl, { method: 'POST', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(validParams), }); if (!response.ok) { return { content: [ { type: 'text', text: `Error searching issues. Error: ${JSON.stringify(await response.json())}`, }, ], isError: true, }; } const data = await response.json(); if (data.issues.length === 0) { return { content: [ { type: 'text', text: `No issues found for query.`, }, ], isError: true, }; } let issues: string[] = []; for (const i of data.issues) { const response = await fetch(i.self, { headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, }); const issue: Issue = await response.json(); const formattedIssue = formatIssue(issue); issues.push(formattedIssue); } return { content: [ { type: 'text', text: `${issues}`, }, ], }; }, }; const createIssue: Tool = { schema: { name: 'create_issue', description: 'Create a new issue', inputSchema: zodToJsonSchema(createIssueSchema), }, handle: async (params) => { let validParams; const result = createIssueSchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], }; } else { validParams = result.data; } const issueUrl = `${process.env.JIRA_PROJECT_URL}/issue/`; const response = await fetch(issueUrl, { method: 'POST', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(validParams), }); if (!response.ok) { return { content: [ { type: 'text', text: `Error creating issue. Error: ${JSON.stringify(await response.json())}`, }, ], isError: true, }; } const data = await response.json(); return { content: [ { type: 'text', text: `Issue created successfully. ${jiraProjectUrl()}/browse/${data.key}`, }, ], }; }, }; const assignIssue: Tool = { schema: { name: 'assign_issue', description: 'Assign an issue to a user.', inputSchema: zodToJsonSchema(assignIssueSchema), }, handle: async (params) => { let validParams; const result = assignIssueSchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], }; } else { validParams = result.data; } const userSearchUrl = `${process.env.JIRA_PROJECT_URL}/user/search?query=${validParams.userQuery}`; const userResponse = await fetch(userSearchUrl, { headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, }); if (!userResponse.ok) { return { content: [ { type: 'text', text: `There was an error locating a user. Error: ${JSON.stringify( await userResponse.json() )}`, }, ], isError: true, }; } const userData = await userResponse.json(); const user = userResponseSchema.parse(userData); if (user.length === 0) { return { content: [ { type: 'text', text: `No user found for ${validParams.userQuery}`, }, ], isError: true, }; } const assignIssueUrl = `${process.env.JIRA_PROJECT_URL}/issue/${validParams.issueKey}/assignee`; const assignResponse = await fetch(assignIssueUrl, { method: 'PUT', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ accountId: user[0].accountId }), }); if (!assignResponse.ok) { return { content: [ { type: 'text', text: `There was an error assigning ${validParams.issueKey} to ${user[0].displayName}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Issue ${validParams.issueKey} assigned to ${user[0].displayName}`, }, ], }; }, }; const unassignIssue: Tool = { schema: { name: 'unassign_issue', description: 'Unassign an issue.', inputSchema: zodToJsonSchema(unassignIssueSchema), }, handle: async (params) => { let validParams; const result = unassignIssueSchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], }; } else { validParams = result.data; } const unassignIssueUrl = `${process.env.JIRA_PROJECT_URL}/issue/${validParams.issueKey}/assignee`; const response = await fetch(unassignIssueUrl, { method: 'PUT', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ accountId: -1 }), }); if (!response.ok) { return { content: [ { type: 'text', text: `Error unassigning issue. Error: ${JSON.stringify(await response.json())}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Issue ${validParams.issueKey} successfully unassigned.`, }, ], }; }, }; const editIssue: Tool = { schema: { name: 'edit_issue', description: 'Edit the fields of an issue.', inputSchema: zodToJsonSchema(editIssueSchema), }, handle: async (params) => { let validParams; const result = editIssueSchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], isError: true, }; } else { validParams = result.data; } const payload = await updatePayload(validParams.fieldsToUpdate); if (!payload) { return { content: [ { type: 'text', text: 'Unable to create payload.', }, ], isError: true, }; } const updateIssueUrl = `${process.env.JIRA_PROJECT_URL}/issue/${validParams.issueKey}`; const updateResponse = await fetch(updateIssueUrl, { method: 'PUT', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify(payload), }); if (!updateResponse.ok) { return { content: [ { type: 'text', text: `Error updating issue ${ validParams.issueKey }. Error: ${await updateResponse.json()}`, }, ], }; } return { content: [ { type: 'text', text: `Issue ${validParams.issueKey} updated successfully.`, }, ], }; }, }; const transitionIssue: Tool = { schema: { name: 'transition_issue', description: 'Transition an issue to another status.', inputSchema: zodToJsonSchema(transitionIssueSchema), }, handle: async (params) => { let validParams; const result = transitionIssueSchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], isError: true, }; } else { validParams = result.data; } const getTransitionsResponse = await fetch( `${process.env.JIRA_PROJECT_URL}/issue/${validParams.issueKey}/transitions`, { headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, } ); if (!getTransitionsResponse.ok) { return { content: [ { type: 'text', text: `Issue retrieving transitions for ${ validParams.issueKey }. Error: ${await getTransitionsResponse.json()}`, }, ], isError: true, }; } let issueTransitions; const transitionsResult = transitionsSchema.safeParse(await getTransitionsResponse.json()); if (!transitionsResult.success) { return { content: [ { type: 'text', text: `Error parsing transitions. Error: ${transitionsResult.error.message}`, }, ], isError: true, }; } else { issueTransitions = transitionsResult.data; } const transition = issueTransitions.transitions.filter((transition) => { return transition.name.toLowerCase() === validParams.transition.toLowerCase(); }); if (transition.length === 0) { const allTransitions = issueTransitions.transitions.map((transition) => { return transition.name; }); return { content: [ { type: 'text', text: `No transition matching "${ validParams.transition }". The following transitions are available:\n${allTransitions.join('\n')}`, }, ], }; } const transitionIssueResponse = await fetch( `${process.env.JIRA_PROJECT_URL}/issue/${validParams.issueKey}/transitions`, { method: 'POST', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ transition: { id: transition[0].id } }), } ); if (!transitionIssueResponse.ok) { return { content: [ { type: 'text', text: `Error transitioning issue. Error: ${await transitionIssueResponse.json()}`, }, ], isError: true, }; } return { content: [ { type: 'text', text: `Issue ${validParams.issueKey} transitioned to ${transition[0].name}`, }, ], }; }, }; const archiveIssues: Tool = { schema: { name: 'archive_issues', description: 'Archive one or more issues by keys or JQL', inputSchema: zodToJsonSchema(archiveIssueSchema), }, handle: async (params) => { let validParams; const result = archiveIssueSchema.safeParse(params); if (!result.success) { return { content: [ { type: 'text', text: `Error parsing parameters. Error: ${result.error.message}`, }, ], isError: true, }; } else { validParams = result.data; } let archiveResponse; if (validParams.issueIdsOrKeys.length > 0) { const archiveIssueByKeysResponse = await fetch( `${process.env.JIRA_PROJECT_URL}/issue/archive`, { method: 'PUT', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ issueIdsOrKeys: validParams.issueIdsOrKeys }), } ); if (!archiveIssueByKeysResponse.ok) { return { content: [ { type: 'text', text: `Error archiving issues by keys. Error: ${await archiveIssueByKeysResponse.json()}`, }, ], isError: true, }; } const validArchiveByKeysResponse = archiveResponseSchema.safeParse( await archiveIssueByKeysResponse.json() ); if (!validArchiveByKeysResponse.success) { return { content: [ { type: 'text', text: `Error parsing archive response. Error: ${validArchiveByKeysResponse.error.message}`, }, ], isError: true, }; } archiveResponse = validArchiveByKeysResponse.data; } else { const archiveIssuesByJqlResponse = await fetch( `${process.env.JIRA_PROJECT_URL}/issue/archive`, { method: 'POST', headers: { Authorization: `Basic ${apiKey}`, 'Content-Type': 'application/json', }, body: JSON.stringify({ jql: validParams.jql }), } ); if (!archiveIssuesByJqlResponse.ok) { return { content: [ { type: 'text', text: `Error archiving issues by keys. Error: ${await archiveIssuesByJqlResponse.json()}`, }, ], isError: true, }; } archiveResponse = { message: `View archive status at ${archiveIssuesByJqlResponse.body}` }; } return { content: [ { type: 'text', text: `Issue(s) archived successfully.\n${JSON.stringify(archiveResponse)}`, }, ], }; }, }; export default [ getIssueByKey, searchIssues, createIssue, assignIssue, unassignIssue, editIssue, transitionIssue, archiveIssues, ];

Latest Blog Posts

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/brianstone/jira-mcp-server'

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