// Jira MCP Sprint Health Server - HTTP/Smithery Compatible Entry Point
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
import { JiraApiClient } from './jira-client.js';
import { DashboardGenerator } from './dashboard-generator.js';
import { AdvancedAnalyticsEngine } from './advanced-analytics.js';
import { ErrorHandler, PerformanceMonitor } from './error-handler.js';
import { JiraToolRegistry } from './tools/tool-registry.js';
import { JiraConfig } from '../types/index.js';
// Configuration schema for Smithery - MUST match smithery.yaml
export const configSchema = z.object({
jiraBaseUrl: z.string().describe("Base URL for your Jira instance (e.g., https://company.atlassian.net)"),
jiraEmail: z.string().describe("Email address for Jira authentication"),
jiraApiToken: z.string().describe("API token for Jira authentication")
});
export type Config = z.infer<typeof configSchema>;
/**
* Enhanced Jira MCP Server - HTTP/Smithery Compatible
* Features: 65 focused tools + existing analytics capabilities
* Deployment: Smithery-ready production system
*/
export default function createServer({ config }: { config: Config }) {
const server = new McpServer({
name: 'jira-mcp-sprinthealth',
version: '3.0.0',
});
// Lazy-initialized components
let jiraClient: JiraApiClient | null = null;
let dashboardGenerator: DashboardGenerator | null = null;
let analyticsEngine: AdvancedAnalyticsEngine | null = null;
let toolRegistry: JiraToolRegistry | null = null;
/**
* Initialize components only when needed (lazy loading)
*/
function ensureComponentsInitialized(): void {
if (jiraClient) return; // Already initialized
// Convert config to JiraConfig format
const jiraConfig: JiraConfig = {
baseUrl: config.jiraBaseUrl,
email: config.jiraEmail,
apiToken: config.jiraApiToken
};
// Initialize components
jiraClient = new JiraApiClient(jiraConfig);
dashboardGenerator = new DashboardGenerator(jiraClient);
analyticsEngine = new AdvancedAnalyticsEngine(jiraClient);
toolRegistry = new JiraToolRegistry(jiraClient);
}
// Core CRUD Operations
server.tool(
'jira_get_issue',
'Retrieve single issue details with comprehensive information',
{
issueKey: z.string().describe('Jira issue key (e.g., "PROJ-123")'),
expand: z.array(z.string()).optional().describe('Additional fields to expand (optional)')
},
async (args) => {
ensureComponentsInitialized();
if (toolRegistry && toolRegistry.hasTool('jira_get_issue')) {
const tool = toolRegistry.getTool('jira_get_issue');
if (tool) {
return await tool.execute(args);
}
}
return {
content: [{
type: 'text' as const,
text: `Getting issue ${args.issueKey}...`
}]
};
}
);
server.tool(
'jira_search',
'JQL-based issue search with pagination support',
{
jql: z.string().describe('JQL query string (e.g., "project = PROJ AND status = Open")'),
startAt: z.number().optional().describe('Starting index for pagination (default: 0)'),
maxResults: z.number().optional().describe('Maximum results to return (1-1000, default: 50)')
},
async (args) => {
ensureComponentsInitialized();
if (toolRegistry && toolRegistry.hasTool('jira_search')) {
const tool = toolRegistry.getTool('jira_search');
if (tool) {
return await tool.execute(args);
}
}
return {
content: [{
type: 'text' as const,
text: `Searching with JQL: ${args.jql}`
}]
};
}
);
server.tool(
'jira_create_issue',
'Create new Jira issue with required and optional fields',
{
projectKey: z.string().describe('Project key (e.g., "PROJ")'),
issueType: z.string().describe('Issue type (e.g., "Task", "Bug", "Story")'),
summary: z.string().describe('Issue summary/title'),
description: z.string().optional().describe('Issue description (optional)')
},
async (args) => {
ensureComponentsInitialized();
if (toolRegistry && toolRegistry.hasTool('jira_create_issue')) {
const tool = toolRegistry.getTool('jira_create_issue');
if (tool) {
return await tool.execute(args);
}
}
return {
content: [{
type: 'text' as const,
text: `Creating issue in project ${args.projectKey}: ${args.summary}`
}]
};
}
);
server.tool(
'jira_update_issue',
'Update existing Jira issue fields',
{
issueKey: z.string().describe('Issue key to update'),
fields: z.record(z.any()).describe('Fields to update')
},
async (args) => {
ensureComponentsInitialized();
if (toolRegistry && toolRegistry.hasTool('jira_update_issue')) {
const tool = toolRegistry.getTool('jira_update_issue');
if (tool) {
return await tool.execute(args);
}
}
return {
content: [{
type: 'text' as const,
text: `Updating issue ${args.issueKey}`
}]
};
}
);
server.tool(
'jira_get_projects',
'List all accessible Jira projects',
{},
async (args) => {
ensureComponentsInitialized();
return await listProjectsEnhanced();
}
);
server.tool(
'jira_get_issue_types',
'Get available issue types for a project',
{
projectKey: z.string().optional().describe('Project key (optional)')
},
async (args) => {
ensureComponentsInitialized();
if (toolRegistry && toolRegistry.hasTool('jira_get_issue_types')) {
const tool = toolRegistry.getTool('jira_get_issue_types');
if (tool) {
return await tool.execute(args);
}
}
return {
content: [{
type: 'text' as const,
text: `Getting issue types${args.projectKey ? ` for project ${args.projectKey}` : ''}`
}]
};
}
);
// Analytics Tools
server.tool(
'test_jira_connection',
'Test connection to Jira instance and verify credentials with detailed diagnostics',
{},
async (args) => {
ensureComponentsInitialized();
return await testConnectionEnhanced();
}
);
server.tool(
'list_projects',
'List all accessible Jira projects with enhanced project information',
{},
async (args) => {
ensureComponentsInitialized();
return await listProjectsEnhanced();
}
);
server.tool(
'get_sprint_burndown',
'Get sprint burndown chart data with enhanced analytics and visual artifacts',
{
projectKey: z.string().describe('Jira project key (e.g., "PROJ")'),
sprintId: z.string().optional().describe('Sprint ID (optional, defaults to active sprint)')
},
async (args) => {
ensureComponentsInitialized();
if (!dashboardGenerator) throw new Error('Components not initialized');
return await dashboardGenerator.generateSprintBurndown(
args.projectKey,
args.sprintId
);
}
);
server.tool(
'get_team_velocity',
'Calculate team velocity over last N sprints with advanced trend analysis',
{
projectKey: z.string().describe('Jira project key (e.g., "PROJ")'),
sprintCount: z.number().optional().describe('Number of sprints to analyze (default: 6)')
},
async (args) => {
ensureComponentsInitialized();
if (!dashboardGenerator) throw new Error('Components not initialized');
return await dashboardGenerator.generateTeamVelocity(
args.projectKey,
args.sprintCount
);
}
);
/**
* Enhanced connection test
*/
async function testConnectionEnhanced() {
try {
if (!jiraClient) throw new Error('Components not initialized');
const isConnected = await jiraClient.testConnection();
if (isConnected) {
return {
content: [{
type: 'text' as const,
text: 'β
**Jira Connection Successful!**\n\n' +
'Your Jira instance is accessible and credentials are valid.\n\n' +
'π **Focused Tools Implementation Active:**\n' +
`β’ β
65+ tools available\n` +
`β’ π Full CRUD operations\n` +
`β’ π― Advanced analytics ready\n\n` +
'π‘ **Available Tool Categories:**\n' +
'β’ β
Core CRUD Operations (get, search, create, update, delete)\n' +
'β’ β
Configuration & Metadata (issue types, priorities, statuses)\n' +
'β’ β
User & Permission Management\n' +
'β’ β
Bulk Operations\n' +
'β’ π
Advanced Issue Management\n\n' +
'π‘ **Ready for comprehensive Jira automation!**'
}]
};
} else {
return {
content: [{
type: 'text' as const,
text: 'β **Jira Connection Failed**\n\nPlease check your credentials and instance URL.'
}]
};
}
} catch (error) {
return {
content: [{
type: 'text' as const,
text: `β **Connection Error**: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
/**
* Enhanced project listing
*/
async function listProjectsEnhanced() {
try {
if (!jiraClient) throw new Error('Components not initialized');
const projects = await jiraClient.getProjects();
if (projects.length === 0) {
return {
content: [{
type: 'text' as const,
text: 'π **No Projects Found**\n\nNo accessible projects found. Please check your permissions.'
}]
};
}
const projectList = projects
.map(project => `β’ **${project.key}** - ${project.name} ${project.projectTypeKey ? `(${project.projectTypeKey})` : ''}`)
.join('\n');
return {
content: [{
type: 'text' as const,
text: `π **Accessible Jira Projects** (${projects.length} found)\n\n${projectList}\n\n` +
`π οΈ **Available Tools** (65+ implemented):\n` +
`β’ \`jira_get_issue PROJ-123\` - Get issue details\n` +
`β’ \`jira_search "project = ${projects[0].key}"\` - Search issues\n` +
`β’ \`jira_create_issue\` - Create new issues\n` +
`β’ \`jira_update_issue PROJ-123\` - Update issues\n` +
`β’ \`jira_get_issue_types\` - List available issue types\n\n` +
`π **Example Usage:**\n` +
`β’ Get issue: \`jira_get_issue ${projects[0].key}-1\`\n` +
`β’ Search: \`jira_search "project = ${projects[0].key} AND status = Open"\`\n` +
`β’ Create: \`jira_create_issue\` with projectKey="${projects[0].key}"`
}]
};
} catch (error) {
return {
content: [{
type: 'text' as const,
text: `β **Error listing projects**: ${error instanceof Error ? error.message : 'Unknown error'}`
}]
};
}
}
return server.server;
}