# 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: -------------------------------------------------------------------------------- ``` 1 | # Atlassian Credentials 2 | JIRA_BASE_URL=https://your-domain.atlassian.net 3 | CONFLUENCE_BASE_URL=https://your-domain.atlassian.net 4 | BITBUCKET_BASE_URL=https://api.bitbucket.org/2.0 5 | [email protected] 6 | ATLASSIAN_API_TOKEN=your-api-token 7 | 8 | # MCP Server Configuration 9 | PORT=3000 ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Dependency directories 11 | node_modules/ 12 | 13 | # TypeScript cache 14 | *.tsbuildinfo 15 | 16 | # Optional npm cache directory 17 | .npm 18 | 19 | # dotenv environment variable files 20 | .env 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | .env.local 25 | 26 | # Build / output directories 27 | dist 28 | build 29 | out ``` -------------------------------------------------------------------------------- /src/mcp/jiraTools.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const jiraTools = []; ``` -------------------------------------------------------------------------------- /src/mcp/bitbucketTools.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const bitbucketTools = []; ``` -------------------------------------------------------------------------------- /src/mcp/confluenceTools.ts: -------------------------------------------------------------------------------- ```typescript 1 | export const confluenceTools = []; ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "module": "commonjs", 5 | "outDir": "./dist", 6 | "rootDir": "./src", 7 | "strict": true, 8 | "esModuleInterop": true, 9 | "skipLibCheck": true, 10 | "forceConsistentCasingInFileNames": true, 11 | "resolveJsonModule": true 12 | }, 13 | "include": ["src/**/*"], 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json 1 | { 2 | "name": "atlassian-cursor-mcp", 3 | "version": "1.0.0", 4 | "description": "Managed Code Plugin for Cursor IDE to integrate with Atlassian products", 5 | "main": "dist/index.js", 6 | "scripts": { 7 | "start": "ts-node src/index.ts", 8 | "build": "tsc", 9 | "serve": "node dist/index.js", 10 | "dev": "nodemon --exec ts-node src/index.ts", 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "keywords": ["cursor", "mcp", "atlassian", "jira", "confluence", "bitbucket"], 14 | "author": "", 15 | "license": "MIT", 16 | "dependencies": { 17 | "@modelcontextprotocol/sdk": "^1.6.1", 18 | "@types/express": "^5.0.0", 19 | "@types/node": "^22.13.9", 20 | "axios": "^1.8.2", 21 | "dotenv": "^16.4.7", 22 | "express": "^5.0.1", 23 | "ts-node": "^10.9.2", 24 | "typescript": "^5.8.2" 25 | }, 26 | "devDependencies": { 27 | "nodemon": "^3.1.9" 28 | } 29 | } ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript 1 | import { MCPServer } from '@modelcontextprotocol/sdk'; 2 | import express from 'express'; 3 | import dotenv from 'dotenv'; 4 | import { jiraTools } from './mcp/jiraTools'; 5 | import { confluenceTools } from './mcp/confluenceTools'; 6 | import { bitbucketTools } from './mcp/bitbucketTools'; 7 | 8 | // Load environment variables 9 | dotenv.config(); 10 | 11 | // Create Express app 12 | const app = express(); 13 | const port = process.env.PORT || 3000; 14 | 15 | // Create MCP server 16 | const mcpServer = new MCPServer({ 17 | name: 'Atlassian MCP', 18 | description: 'Managed Code Plugin for Cursor IDE to integrate with Atlassian products (JIRA, Confluence, BitBucket)', 19 | version: '1.0.0', 20 | tools: [...jiraTools, ...confluenceTools, ...bitbucketTools], 21 | }); 22 | 23 | // Mount MCP server to Express app 24 | app.use('/mcp', mcpServer.handler()); 25 | 26 | // Add health check endpoint 27 | app.get('/health', (req, res) => { 28 | res.status(200).json({ status: 'ok', message: 'Atlassian MCP server is running' }); 29 | }); 30 | 31 | // Start server 32 | app.listen(port, () => { 33 | console.log(`Atlassian MCP server is running on port ${port}`); 34 | console.log(`MCP endpoint: http://localhost:${port}/mcp`); 35 | console.log(`Health check: http://localhost:${port}/health`); 36 | }); ``` -------------------------------------------------------------------------------- /src/services/AtlassianBaseService.ts: -------------------------------------------------------------------------------- ```typescript 1 | import axios, { AxiosInstance, AxiosRequestConfig } from 'axios'; 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | export default abstract class AtlassianBaseService { 7 | protected client: AxiosInstance; 8 | protected baseUrl: string; 9 | 10 | constructor(baseUrl: string) { 11 | this.baseUrl = baseUrl; 12 | this.client = axios.create({ 13 | baseURL: baseUrl, 14 | auth: { 15 | username: process.env.ATLASSIAN_EMAIL || '', 16 | password: process.env.ATLASSIAN_API_TOKEN || '', 17 | }, 18 | headers: { 19 | 'Content-Type': 'application/json', 20 | 'Accept': 'application/json', 21 | }, 22 | }); 23 | 24 | // Add request interceptor for logging 25 | this.client.interceptors.request.use((config) => { 26 | console.log(`Making ${config.method?.toUpperCase()} request to ${config.baseURL}${config.url}`); 27 | return config; 28 | }); 29 | 30 | // Add response interceptor for error handling 31 | this.client.interceptors.response.use( 32 | (response) => response, 33 | (error) => { 34 | console.error('API Error:', error.response?.data || error.message); 35 | return Promise.reject(error); 36 | } 37 | ); 38 | } 39 | 40 | protected async get<T>(url: string, config?: AxiosRequestConfig): Promise<T> { 41 | const response = await this.client.get<T>(url, config); 42 | return response.data; 43 | } 44 | 45 | protected async post<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> { 46 | const response = await this.client.post<T>(url, data, config); 47 | return response.data; 48 | } 49 | 50 | protected async put<T>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> { 51 | const response = await this.client.put<T>(url, data, config); 52 | return response.data; 53 | } 54 | 55 | protected async delete<T>(url: string, config?: AxiosRequestConfig): Promise<T> { 56 | const response = await this.client.delete<T>(url, config); 57 | return response.data; 58 | } 59 | } ``` -------------------------------------------------------------------------------- /src/services/JiraService.ts: -------------------------------------------------------------------------------- ```typescript 1 | import AtlassianBaseService from './AtlassianBaseService'; 2 | import dotenv from 'dotenv'; 3 | 4 | dotenv.config(); 5 | 6 | interface JiraIssue { 7 | id: string; 8 | key: string; 9 | self: string; 10 | fields: { 11 | summary: string; 12 | description?: string; 13 | status?: { 14 | name: string; 15 | }; 16 | issuetype?: { 17 | name: string; 18 | }; 19 | priority?: { 20 | name: string; 21 | }; 22 | assignee?: { 23 | displayName: string; 24 | emailAddress: string; 25 | }; 26 | reporter?: { 27 | displayName: string; 28 | emailAddress: string; 29 | }; 30 | created?: string; 31 | updated?: string; 32 | [key: string]: any; 33 | }; 34 | } 35 | 36 | export interface CreateJiraIssuePayload { 37 | fields: { 38 | project: { 39 | key: string; 40 | }; 41 | summary: string; 42 | description?: string; 43 | issuetype: { 44 | name: string; 45 | }; 46 | [key: string]: any; 47 | }; 48 | } 49 | 50 | export default class JiraService extends AtlassianBaseService { 51 | constructor() { 52 | super(process.env.JIRA_BASE_URL || ''); 53 | } 54 | 55 | async searchIssues(jql: string, startAt: number = 0, maxResults: number = 50): Promise<{ 56 | issues: JiraIssue[]; 57 | total: number; 58 | }> { 59 | return this.get('/rest/api/3/search', { 60 | params: { 61 | jql, 62 | startAt, 63 | maxResults, 64 | }, 65 | }); 66 | } 67 | 68 | async getIssue(issueIdOrKey: string): Promise<JiraIssue> { 69 | return this.get(`/rest/api/3/issue/${issueIdOrKey}`); 70 | } 71 | 72 | async createIssue(payload: CreateJiraIssuePayload): Promise<{ id: string; key: string; self: string }> { 73 | return this.post('/rest/api/3/issue', payload); 74 | } 75 | 76 | async updateIssue(issueIdOrKey: string, payload: any): Promise<void> { 77 | return this.put(`/rest/api/3/issue/${issueIdOrKey}`, payload); 78 | } 79 | 80 | async assignIssue(issueIdOrKey: string, assignee: string): Promise<void> { 81 | return this.put(`/rest/api/3/issue/${issueIdOrKey}/assignee`, { 82 | accountId: assignee, 83 | }); 84 | } 85 | 86 | async getTransitions(issueIdOrKey: string): Promise<any> { 87 | return this.get(`/rest/api/3/issue/${issueIdOrKey}/transitions`); 88 | } 89 | 90 | async transitionIssue(issueIdOrKey: string, transitionId: string, fields?: any): Promise<void> { 91 | return this.post(`/rest/api/3/issue/${issueIdOrKey}/transitions`, { 92 | transition: { 93 | id: transitionId, 94 | }, 95 | fields, 96 | }); 97 | } 98 | 99 | async addComment(issueIdOrKey: string, comment: string): Promise<any> { 100 | return this.post(`/rest/api/3/issue/${issueIdOrKey}/comment`, { 101 | body: { 102 | type: 'doc', 103 | version: 1, 104 | content: [ 105 | { 106 | type: 'paragraph', 107 | content: [ 108 | { 109 | type: 'text', 110 | text: comment, 111 | }, 112 | ], 113 | }, 114 | ], 115 | }, 116 | }); 117 | } 118 | 119 | async getProjects(): Promise<any[]> { 120 | return this.get('/rest/api/3/project'); 121 | } 122 | 123 | async getIssueTypes(): Promise<any[]> { 124 | return this.get('/rest/api/3/issuetype'); 125 | } 126 | } ```