# Directory Structure ``` ├── .env.example ├── .gitignore ├── package.json ├── README.md ├── src │ ├── index.ts │ ├── mcp │ │ ├── bitbucketTools.ts │ │ ├── confluenceTools.ts │ │ └── jiraTools.ts │ └── services │ ├── AtlassianBaseService.ts │ └── JiraService.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- ``` # Atlassian Credentials JIRA_BASE_URL=https://your-domain.atlassian.net CONFLUENCE_BASE_URL=https://your-domain.atlassian.net BITBUCKET_BASE_URL=https://api.bitbucket.org/2.0 [email protected] ATLASSIAN_API_TOKEN=your-api-token # MCP Server Configuration PORT=3000 ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* .pnpm-debug.log* # Dependency directories node_modules/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # dotenv environment variable files .env .env.development.local .env.test.local .env.production.local .env.local # Build / output directories dist build out ``` -------------------------------------------------------------------------------- /src/mcp/jiraTools.ts: -------------------------------------------------------------------------------- ```typescript export const jiraTools = []; ``` -------------------------------------------------------------------------------- /src/mcp/bitbucketTools.ts: -------------------------------------------------------------------------------- ```typescript export const bitbucketTools = []; ``` -------------------------------------------------------------------------------- /src/mcp/confluenceTools.ts: -------------------------------------------------------------------------------- ```typescript export const confluenceTools = []; ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2020", "module": "commonjs", "outDir": "./dist", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "resolveJsonModule": true }, "include": ["src/**/*"], "exclude": ["node_modules", "**/*.test.ts"] } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "atlassian-cursor-mcp", "version": "1.0.0", "description": "Managed Code Plugin for Cursor IDE to integrate with Atlassian products", "main": "dist/index.js", "scripts": { "start": "ts-node src/index.ts", "build": "tsc", "serve": "node dist/index.js", "dev": "nodemon --exec ts-node src/index.ts", "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": ["cursor", "mcp", "atlassian", "jira", "confluence", "bitbucket"], "author": "", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.6.1", "@types/express": "^5.0.0", "@types/node": "^22.13.9", "axios": "^1.8.2", "dotenv": "^16.4.7", "express": "^5.0.1", "ts-node": "^10.9.2", "typescript": "^5.8.2" }, "devDependencies": { "nodemon": "^3.1.9" } } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript import { MCPServer } from '@modelcontextprotocol/sdk'; import express from 'express'; import dotenv from 'dotenv'; import { jiraTools } from './mcp/jiraTools'; import { confluenceTools } from './mcp/confluenceTools'; import { bitbucketTools } from './mcp/bitbucketTools'; // Load environment variables dotenv.config(); // Create Express app const app = express(); const port = process.env.PORT || 3000; // Create MCP server const mcpServer = new MCPServer({ name: 'Atlassian MCP', description: 'Managed Code Plugin for Cursor IDE to integrate with Atlassian products (JIRA, Confluence, BitBucket)', version: '1.0.0', tools: [...jiraTools, ...confluenceTools, ...bitbucketTools], }); // Mount MCP server to Express app app.use('/mcp', mcpServer.handler()); // Add health check endpoint app.get('/health', (req, res) => { res.status(200).json({ status: 'ok', message: 'Atlassian MCP server is running' }); }); // Start server app.listen(port, () => { console.log(`Atlassian MCP server is running on port ${port}`); console.log(`MCP endpoint: http://localhost:${port}/mcp`); console.log(`Health check: http://localhost:${port}/health`); }); ``` -------------------------------------------------------------------------------- /src/services/AtlassianBaseService.ts: -------------------------------------------------------------------------------- ```typescript import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; import dotenv from 'dotenv'; dotenv.config(); export default abstract class AtlassianBaseService { protected client: AxiosInstance; protected baseUrl: string; constructor(baseUrl: string) { this.baseUrl = baseUrl; this.client = axios.create({ baseURL: baseUrl, auth: { username: process.env.ATLASSIAN_EMAIL || '', password: process.env.ATLASSIAN_API_TOKEN || '', }, headers: { 'Content-Type': 'application/json', 'Accept': 'application/json', }, }); // Add request interceptor for logging this.client.interceptors.request.use((config) => { console.log(`Making ${config.method?.toUpperCase()} request to ${config.baseURL}${config.url}`); return config; }); // Add response interceptor for error handling this.client.interceptors.response.use( (response) => response, (error) => { console.error('API Error:', error.response?.data || error.message); return Promise.reject(error); } ); } protected async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> { const response = await this.client.get<T>(url, config); return response.data; } protected async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> { const response = await this.client.post<T>(url, data, config); return response.data; } protected async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> { const response = await this.client.put<T>(url, data, config); return response.data; } protected async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> { const response = await this.client.delete<T>(url, config); return response.data; } } ``` -------------------------------------------------------------------------------- /src/services/JiraService.ts: -------------------------------------------------------------------------------- ```typescript import AtlassianBaseService from './AtlassianBaseService'; import dotenv from 'dotenv'; dotenv.config(); interface JiraIssue { id: string; key: string; self: string; fields: { summary: string; description?: string; status?: { name: string; }; issuetype?: { name: string; }; priority?: { name: string; }; assignee?: { displayName: string; emailAddress: string; }; reporter?: { displayName: string; emailAddress: string; }; created?: string; updated?: string; [key: string]: any; }; } export interface CreateJiraIssuePayload { fields: { project: { key: string; }; summary: string; description?: string; issuetype: { name: string; }; [key: string]: any; }; } export default class JiraService extends AtlassianBaseService { constructor() { super(process.env.JIRA_BASE_URL || ''); } async searchIssues(jql: string, startAt: number = 0, maxResults: number = 50): Promise<{ issues: JiraIssue[]; total: number; }> { return this.get('/rest/api/3/search', { params: { jql, startAt, maxResults, }, }); } async getIssue(issueIdOrKey: string): Promise<JiraIssue> { return this.get(`/rest/api/3/issue/${issueIdOrKey}`); } async createIssue(payload: CreateJiraIssuePayload): Promise<{ id: string; key: string; self: string }> { return this.post('/rest/api/3/issue', payload); } async updateIssue(issueIdOrKey: string, payload: any): Promise<void> { return this.put(`/rest/api/3/issue/${issueIdOrKey}`, payload); } async assignIssue(issueIdOrKey: string, assignee: string): Promise<void> { return this.put(`/rest/api/3/issue/${issueIdOrKey}/assignee`, { accountId: assignee, }); } async getTransitions(issueIdOrKey: string): Promise<any> { return this.get(`/rest/api/3/issue/${issueIdOrKey}/transitions`); } async transitionIssue(issueIdOrKey: string, transitionId: string, fields?: any): Promise<void> { return this.post(`/rest/api/3/issue/${issueIdOrKey}/transitions`, { transition: { id: transitionId, }, fields, }); } async addComment(issueIdOrKey: string, comment: string): Promise<any> { return this.post(`/rest/api/3/issue/${issueIdOrKey}/comment`, { body: { type: 'doc', version: 1, content: [ { type: 'paragraph', content: [ { type: 'text', text: comment, }, ], }, ], }, }); } async getProjects(): Promise<any[]> { return this.get('/rest/api/3/project'); } async getIssueTypes(): Promise<any[]> { return this.get('/rest/api/3/issuetype'); } } ```