import express from 'express';
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { createAtlassianClient } from './atlassian.js';
import 'dotenv/config';
const app = express();
app.use(express.json());
const API_KEY = process.env.API_KEY;
function authenticate(req: express.Request, res: express.Response, next: express.NextFunction) {
if (!API_KEY) return next();
const authHeader = req.headers.authorization;
if (!authHeader?.startsWith('Bearer ') || authHeader.substring(7) !== API_KEY) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
app.get('/health', (_req, res) => {
res.json({ status: 'ok', service: 'atlassian-mcp-server' });
});
app.get('/sse', authenticate, async (_req, res) => {
const client = createAtlassianClient();
const server = new Server(
{ name: 'atlassian-mcp-server', version: '1.0.0' },
{ capabilities: { tools: {} } }
);
const tools = [
{ name: 'jira_search_issues', description: 'Search Jira issues using JQL', inputSchema: { type: 'object', properties: { jql: { type: 'string' }, max_results: { type: 'number' } }, required: ['jql'] } },
{ name: 'jira_get_issue', description: 'Get a Jira issue', inputSchema: { type: 'object', properties: { issue_key: { type: 'string' } }, required: ['issue_key'] } },
{ name: 'jira_create_issue', description: 'Create a Jira issue', inputSchema: { type: 'object', properties: { project_key: { type: 'string' }, summary: { type: 'string' }, issue_type: { type: 'string' }, description: { type: 'string' } }, required: ['project_key', 'summary', 'issue_type'] } },
{ name: 'jira_add_comment', description: 'Add comment to issue', inputSchema: { type: 'object', properties: { issue_key: { type: 'string' }, body: { type: 'string' } }, required: ['issue_key', 'body'] } },
{ name: 'jira_list_projects', description: 'List Jira projects', inputSchema: { type: 'object', properties: {} } },
{ name: 'confluence_list_spaces', description: 'List Confluence spaces', inputSchema: { type: 'object', properties: {} } },
{ name: 'confluence_search', description: 'Search Confluence', inputSchema: { type: 'object', properties: { cql: { type: 'string' } }, required: ['cql'] } },
];
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
try {
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_add_comment':
result = await client.addComment((args as { issue_key: string }).issue_key, (args as { body: string }).body);
break;
case 'jira_list_projects':
result = await client.listProjects();
break;
case 'confluence_list_spaces':
result = await client.listSpaces();
break;
case 'confluence_search':
result = await client.searchContent((args as { cql: string }).cql);
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 };
}
});
const transport = new SSEServerTransport('/messages', res);
await server.connect(transport);
});
app.post('/messages', authenticate, (_req, res) => res.json({ received: true }));
const PORT = process.env.PORT || 8000;
app.listen(PORT, () => console.log(`Atlassian MCP server listening on port ${PORT}`));