Skip to main content
Glama
jira-projects.ts8.74 kB
/** * Consolidated Jira Projects Tool. * Combines all project-related operations into a single action-based tool. * @module tools/consolidated/jira-projects */ import { z } from 'zod'; import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { listProjects, getProject, getProjectStatuses, getProjectComponents, } from '../../jira/endpoints/projects.js'; import { getProjectVersions, createVersion, batchCreateVersions, updateVersion, releaseVersion, } from '../../jira/endpoints/versions.js'; import { encodeToon, simplifyProject } from '../../formatters/toon.js'; import { createLogger } from '../../utils/logger.js'; const logger = createLogger('tool-jira-projects'); /** * Schema for the jira_projects tool. */ const jiraProjectsSchema = z.object({ action: z .enum([ 'list', 'get', 'get_statuses', 'get_components', 'get_versions', 'create_version', 'batch_create_versions', 'update_version', 'release_version', ]) .describe('The action to perform'), // Common fields projectKey: z .string() .optional() .describe('Project key (e.g., "PROJ") - required for most actions'), // Get options full: z .boolean() .optional() .default(false) .describe('Return full data instead of minimal fields (default: false)'), // List pagination maxResults: z .number() .optional() .default(50) .describe('Maximum results for list (default: 50)'), startAt: z .number() .optional() .default(0) .describe('Starting index for pagination'), includeArchived: z .boolean() .optional() .default(false) .describe('Include archived projects in list (default: false)'), // Version fields versionId: z .string() .optional() .describe('Version ID for update/release actions'), versionName: z.string().optional().describe('Version name for create'), versionDescription: z.string().optional().describe('Version description'), startDate: z.string().optional().describe('Start date (YYYY-MM-DD)'), releaseDate: z.string().optional().describe('Release date (YYYY-MM-DD)'), released: z.boolean().optional().describe('Whether version is released'), archived: z.boolean().optional().describe('Whether version is archived'), // Batch version creation versions: z .array( z.object({ name: z.string(), description: z.string().optional(), startDate: z.string().optional(), releaseDate: z.string().optional(), }) ) .optional() .describe('Array of versions for batch_create_versions'), }); type JiraProjectsInput = z.infer<typeof jiraProjectsSchema>; /** * Handler for the jira_projects tool. */ async function handleJiraProjects(input: JiraProjectsInput): Promise<string> { const { action } = input; switch (action) { case 'list': { const response = await listProjects( input.startAt ?? 0, input.maxResults ?? 50, input.includeArchived ?? false ); if (input.full) { return JSON.stringify(response, null, 2); } const simplified = response.values.map(simplifyProject); return encodeToon({ projects: simplified, total: response.total, hasMore: !response.isLast, }); } case 'get': { if (!input.projectKey) { throw new Error('projectKey is required for get action'); } const project = await getProject(input.projectKey); if (input.full) { return JSON.stringify(project, null, 2); } return encodeToon(simplifyProject(project)); } case 'get_statuses': { if (!input.projectKey) { throw new Error('projectKey is required for get_statuses action'); } const statuses = await getProjectStatuses(input.projectKey); if (input.full) { return JSON.stringify(statuses, null, 2); } const simplified = statuses.map((item) => ({ issueType: item.issueType.name, statuses: item.statuses.map((s) => s.name), })); return encodeToon({ statusesByType: simplified }); } case 'get_components': { if (!input.projectKey) { throw new Error('projectKey is required for get_components action'); } const components = await getProjectComponents(input.projectKey); if (input.full) { return JSON.stringify(components, null, 2); } const simplified = components.map((c) => ({ id: c.id, name: c.name, })); return encodeToon({ components: simplified }); } case 'get_versions': { if (!input.projectKey) { throw new Error('projectKey is required for get_versions action'); } const versions = await getProjectVersions(input.projectKey); if (input.full) { return JSON.stringify(versions, null, 2); } const simplified = versions.map((v) => ({ id: v.id, name: v.name, released: v.released, releaseDate: v.releaseDate, })); return encodeToon({ versions: simplified }); } case 'create_version': { if (!input.projectKey || !input.versionName) { throw new Error( 'projectKey and versionName are required for create_version action' ); } // Get project ID from key const project = await getProject(input.projectKey); const version = await createVersion({ projectId: project.id, name: input.versionName, description: input.versionDescription, startDate: input.startDate, releaseDate: input.releaseDate, released: input.released, archived: input.archived, }); return encodeToon({ success: true, version: { id: version.id, name: version.name, released: version.released, }, }); } case 'batch_create_versions': { if (!input.projectKey || !input.versions?.length) { throw new Error( 'projectKey and versions array are required for batch_create_versions' ); } const created = await batchCreateVersions( input.projectKey, input.versions ); return encodeToon({ success: true, created: created.map((v) => ({ id: v.id, name: v.name })), }); } case 'update_version': { if (!input.versionId) { throw new Error('versionId is required for update_version action'); } const version = await updateVersion(input.versionId, { name: input.versionName, description: input.versionDescription, startDate: input.startDate, releaseDate: input.releaseDate, released: input.released, archived: input.archived, }); return encodeToon({ success: true, version: { id: version.id, name: version.name }, }); } case 'release_version': { if (!input.versionId) { throw new Error('versionId is required for release_version action'); } const version = await releaseVersion(input.versionId, input.releaseDate); return encodeToon({ success: true, version: { id: version.id, name: version.name, released: version.released, releaseDate: version.releaseDate, }, }); } default: throw new Error(`Unknown action: ${action}`); } } /** * Registers the jira_projects tool with the MCP server. */ export function registerJiraProjectsTool(server: McpServer): void { server.tool( 'jira_projects', `Manage Jira projects and versions. Actions: - list: List all accessible projects - get: Get project details - get_statuses: Get available statuses by issue type - get_components: Get project components - get_versions: Get project versions/releases - create_version: Create a new version - batch_create_versions: Create multiple versions at once - update_version: Update version details - release_version: Mark a version as released`, jiraProjectsSchema.shape, async (params) => { try { const input = jiraProjectsSchema.parse(params); const result = await handleJiraProjects(input); return { content: [{ type: 'text', text: result }] }; } catch (err) { logger.error( 'jira_projects error', err instanceof Error ? err : new Error(String(err)) ); const message = err instanceof Error ? err.message : 'Unknown error'; return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true, }; } } ); }

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/icy-r/jira-mcp'

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