#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createAtlassianClient, AtlassianClient } from './atlassian.js';
import 'dotenv/config';
let client: AtlassianClient;
const server = new Server(
{
name: 'atlassian-mcp-server',
version: '1.0.0',
},
{
capabilities: {
tools: {},
},
}
);
const tools = [
// Jira Issue Tools
{
name: 'jira_search_issues',
description: 'Search Jira issues using JQL (Jira Query Language)',
inputSchema: {
type: 'object',
properties: {
jql: { type: 'string', description: 'JQL query (e.g., "project = PROJ AND status = Open")' },
max_results: { type: 'number', description: 'Maximum results to return (default: 50)' }
},
required: ['jql']
}
},
{
name: 'jira_get_issue',
description: 'Get detailed information about a specific Jira issue',
inputSchema: {
type: 'object',
properties: {
issue_key: { type: 'string', description: 'Issue key (e.g., "PROJ-123")' }
},
required: ['issue_key']
}
},
{
name: 'jira_create_issue',
description: 'Create a new Jira issue',
inputSchema: {
type: 'object',
properties: {
project_key: { type: 'string', description: 'Project key (e.g., "PROJ")' },
summary: { type: 'string', description: 'Issue summary/title' },
issue_type: { type: 'string', description: 'Issue type (e.g., "Bug", "Story", "Task")' },
description: { type: 'string', description: 'Issue description' }
},
required: ['project_key', 'summary', 'issue_type']
}
},
{
name: 'jira_update_issue',
description: 'Update an existing Jira issue',
inputSchema: {
type: 'object',
properties: {
issue_key: { type: 'string', description: 'Issue key (e.g., "PROJ-123")' },
fields: { type: 'object', description: 'Fields to update' }
},
required: ['issue_key', 'fields']
}
},
{
name: 'jira_transition_issue',
description: 'Transition a Jira issue to a different status',
inputSchema: {
type: 'object',
properties: {
issue_key: { type: 'string', description: 'Issue key' },
transition_id: { type: 'string', description: 'Transition ID (use jira_get_transitions to find IDs)' }
},
required: ['issue_key', 'transition_id']
}
},
{
name: 'jira_get_transitions',
description: 'Get available transitions for a Jira issue',
inputSchema: {
type: 'object',
properties: {
issue_key: { type: 'string', description: 'Issue key' }
},
required: ['issue_key']
}
},
{
name: 'jira_add_comment',
description: 'Add a comment to a Jira issue',
inputSchema: {
type: 'object',
properties: {
issue_key: { type: 'string', description: 'Issue key' },
body: { type: 'string', description: 'Comment text' }
},
required: ['issue_key', 'body']
}
},
{
name: 'jira_list_projects',
description: 'List all Jira projects',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'jira_list_boards',
description: 'List all Jira boards',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'jira_list_sprints',
description: 'List sprints for a board',
inputSchema: {
type: 'object',
properties: {
board_id: { type: 'number', description: 'Board ID' }
},
required: ['board_id']
}
},
// Confluence Tools
{
name: 'confluence_list_spaces',
description: 'List all Confluence spaces',
inputSchema: { type: 'object', properties: {} }
},
{
name: 'confluence_get_page',
description: 'Get a Confluence page by ID',
inputSchema: {
type: 'object',
properties: {
page_id: { type: 'string', description: 'Page ID' }
},
required: ['page_id']
}
},
{
name: 'confluence_search',
description: 'Search Confluence content using CQL',
inputSchema: {
type: 'object',
properties: {
cql: { type: 'string', description: 'CQL query (e.g., "type=page AND space=DEV")' }
},
required: ['cql']
}
},
{
name: 'confluence_create_page',
description: 'Create a new Confluence page',
inputSchema: {
type: 'object',
properties: {
space_id: { type: 'string', description: 'Space ID' },
title: { type: 'string', description: 'Page title' },
body: { type: 'string', description: 'Page body (HTML/storage format)' },
parent_id: { type: 'string', description: 'Parent page ID (optional)' }
},
required: ['space_id', 'title', 'body']
}
},
{
name: 'confluence_update_page',
description: 'Update an existing Confluence page',
inputSchema: {
type: 'object',
properties: {
page_id: { type: 'string', description: 'Page ID' },
title: { type: 'string', description: 'New title' },
body: { type: 'string', description: 'New body content' },
version: { type: 'number', description: 'Current version number' }
},
required: ['page_id', 'title', 'body', 'version']
}
}
];
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
if (!client) {
client = createAtlassianClient();
}
let result: unknown;
switch (name) {
case 'jira_search_issues':
result = await client.searchIssues(
(args as { jql: string }).jql,
(args as { max_results?: number }).max_results
);
break;
case 'jira_get_issue':
result = await client.getIssue((args as { issue_key: string }).issue_key);
break;
case 'jira_create_issue': {
const { project_key, summary, issue_type, description } = args as {
project_key: string; summary: string; issue_type: string; description?: string;
};
result = await client.createIssue({
fields: {
project: { key: project_key },
summary,
issuetype: { name: issue_type },
...(description && {
description: {
type: 'doc', version: 1,
content: [{ type: 'paragraph', content: [{ type: 'text', text: description }] }]
}
})
}
});
break;
}
case 'jira_update_issue': {
const { issue_key, fields } = args as { issue_key: string; fields: Record<string, unknown> };
result = await client.updateIssue(issue_key, { fields });
break;
}
case 'jira_transition_issue': {
const { issue_key, transition_id } = args as { issue_key: string; transition_id: string };
result = await client.transitionIssue(issue_key, transition_id);
break;
}
case 'jira_get_transitions':
result = await client.getTransitions((args as { issue_key: string }).issue_key);
break;
case 'jira_add_comment': {
const { issue_key, body } = args as { issue_key: string; body: string };
result = await client.addComment(issue_key, body);
break;
}
case 'jira_list_projects':
result = await client.listProjects();
break;
case 'jira_list_boards':
result = await client.listBoards();
break;
case 'jira_list_sprints':
result = await client.listSprints((args as { board_id: number }).board_id);
break;
case 'confluence_list_spaces':
result = await client.listSpaces();
break;
case 'confluence_get_page':
result = await client.getPage((args as { page_id: string }).page_id);
break;
case 'confluence_search':
result = await client.searchContent((args as { cql: string }).cql);
break;
case 'confluence_create_page': {
const { space_id, title, body, parent_id } = args as {
space_id: string; title: string; body: string; parent_id?: string;
};
result = await client.createPage(space_id, title, body, parent_id);
break;
}
case 'confluence_update_page': {
const { page_id, title, body, version } = args as {
page_id: string; title: string; body: string; version: number;
};
result = await client.updatePage(page_id, title, body, version);
break;
}
default:
throw new Error(`Unknown tool: ${name}`);
}
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }]
};
} catch (error) {
return {
content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : String(error)}` }],
isError: true
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Atlassian MCP server running on stdio');
}
main().catch(console.error);