This is page 1 of 4. Use http://codebase.md/phuc-nt/mcp-atlassian-server?lines=true&page={x} to view the full context.
# Directory Structure
```
├── .env.example
├── .gitignore
├── .npmignore
├── assets
│ ├── atlassian_logo_icon.png
│ └── atlassian_logo_icon.webp
├── CHANGELOG.md
├── dev_mcp-atlassian-test-client
│ ├── package-lock.json
│ ├── package.json
│ ├── src
│ │ ├── list-mcp-inventory.ts
│ │ ├── test-confluence-pages.ts
│ │ ├── test-confluence-spaces.ts
│ │ ├── test-jira-issues.ts
│ │ ├── test-jira-projects.ts
│ │ ├── test-jira-users.ts
│ │ └── tool-test.ts
│ └── tsconfig.json
├── docker-compose.yml
├── Dockerfile
├── docs
│ ├── dev-guide
│ │ ├── advance-resource-tool-2.md
│ │ ├── advance-resource-tool-3.md
│ │ ├── advance-resource-tool.md
│ │ ├── confluence-migrate-to-v2.md
│ │ ├── github-community-exchange.md
│ │ ├── marketplace-publish-application-template.md
│ │ ├── marketplace-publish-guideline.md
│ │ ├── mcp-client-for-testing.md
│ │ ├── mcp-overview.md
│ │ ├── migrate-api-v2-to-v3.md
│ │ ├── mini-plan-refactor-tools.md
│ │ ├── modelcontextprotocol-architecture.md
│ │ ├── modelcontextprotocol-introduction.md
│ │ ├── modelcontextprotocol-resources.md
│ │ ├── modelcontextprotocol-tools.md
│ │ ├── one-click-setup.md
│ │ ├── prompts.md
│ │ ├── release-with-prebuild-bundle.md
│ │ ├── resource-metadata-schema-guideline.md
│ │ ├── resources.md
│ │ ├── sampling.md
│ │ ├── schema-metadata.md
│ │ ├── stdio-transport.md
│ │ ├── tool-vs-resource.md
│ │ ├── tools.md
│ │ └── workflow-examples.md
│ ├── introduction
│ │ ├── marketplace-submission.md
│ │ └── resources-and-tools.md
│ ├── knowledge
│ │ ├── 01-mcp-overview-architecture.md
│ │ ├── 02-mcp-tools-resources.md
│ │ ├── 03-mcp-prompts-sampling.md
│ │ ├── building-mcp-server.md
│ │ └── client-development-guide.md
│ ├── plan
│ │ ├── history.md
│ │ ├── roadmap.md
│ │ └── todo.md
│ └── test-reports
│ ├── cline-installation-test-2025-05-04.md
│ └── cline-test-2025-04-20.md
├── jest.config.js
├── LICENSE
├── llms-install-bundle.md
├── llms-install.md
├── package-lock.json
├── package.json
├── README.md
├── RELEASE_NOTES.md
├── smithery.yaml
├── src
│ ├── index.ts
│ ├── resources
│ │ ├── confluence
│ │ │ ├── index.ts
│ │ │ ├── pages.ts
│ │ │ └── spaces.ts
│ │ ├── index.ts
│ │ └── jira
│ │ ├── boards.ts
│ │ ├── dashboards.ts
│ │ ├── filters.ts
│ │ ├── index.ts
│ │ ├── issues.ts
│ │ ├── projects.ts
│ │ ├── sprints.ts
│ │ └── users.ts
│ ├── schemas
│ │ ├── common.ts
│ │ ├── confluence.ts
│ │ └── jira.ts
│ ├── tests
│ │ ├── confluence
│ │ │ └── create-page.test.ts
│ │ └── e2e
│ │ └── mcp-server.test.ts
│ ├── tools
│ │ ├── confluence
│ │ │ ├── add-comment.ts
│ │ │ ├── create-page.ts
│ │ │ ├── delete-footer-comment.ts
│ │ │ ├── delete-page.ts
│ │ │ ├── update-footer-comment.ts
│ │ │ ├── update-page-title.ts
│ │ │ └── update-page.ts
│ │ ├── index.ts
│ │ └── jira
│ │ ├── add-gadget-to-dashboard.ts
│ │ ├── add-issue-to-sprint.ts
│ │ ├── add-issues-to-backlog.ts
│ │ ├── assign-issue.ts
│ │ ├── close-sprint.ts
│ │ ├── create-dashboard.ts
│ │ ├── create-filter.ts
│ │ ├── create-issue.ts
│ │ ├── create-sprint.ts
│ │ ├── delete-filter.ts
│ │ ├── get-gadgets.ts
│ │ ├── rank-backlog-issues.ts
│ │ ├── remove-gadget-from-dashboard.ts
│ │ ├── start-sprint.ts
│ │ ├── transition-issue.ts
│ │ ├── update-dashboard.ts
│ │ ├── update-filter.ts
│ │ └── update-issue.ts
│ └── utils
│ ├── atlassian-api-base.ts
│ ├── confluence-interfaces.ts
│ ├── confluence-resource-api.ts
│ ├── confluence-tool-api.ts
│ ├── error-handler.ts
│ ├── jira-interfaces.ts
│ ├── jira-resource-api.ts
│ ├── jira-tool-api-agile.ts
│ ├── jira-tool-api-v3.ts
│ ├── jira-tool-api.ts
│ ├── logger.ts
│ ├── mcp-core.ts
│ └── mcp-helpers.ts
├── start-docker.sh
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
```
1 | # Atlassian Configuration
2 | ATLASSIAN_SITE_NAME=your-site.atlassian.net
3 | [email protected]
4 | ATLASSIAN_API_TOKEN=your-api-token
5 |
6 | # MCP Configuration
7 | MCP_SERVER_NAME=mcp-atlassian-integration
8 | MCP_SERVER_VERSION=1.0.0
9 |
10 | # Logging Configuration
11 | LOG_LEVEL=info
12 |
```
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
```
1 | # Development directories
2 | node_modules/
3 | src/
4 | tests/
5 | dev_mcp-atlassian-test-client/
6 |
7 | # Configuration files
8 | .env
9 | .env.example
10 | .gitignore
11 | tsconfig.json
12 | jest.config.js
13 |
14 | # Docker files
15 | Dockerfile
16 | docker-compose.yml
17 | start-docker.sh
18 |
19 | # Development files
20 | *.log
21 | *.tsbuildinfo
22 | .vscode/
23 | .idea/
24 |
25 | # Test files
26 | coverage/
27 | *.test.ts
28 | *.spec.ts
29 |
30 | # Documentation (keep only essential docs)
31 | docs/dev-guide/
32 | docs/test-reports/
33 | docs/plan/
34 |
35 | # Other files
36 | .git/
37 | .github/
38 |
```
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | phuc-nt-mcp-atlassian-server-2.0.0.tgz
2 | phuc-nt-mcp-atlassian-server-*.tgz
3 |
4 | # Node modules
5 | node_modules/
6 |
7 | # Build files
8 | dist/
9 |
10 | # Coverage reports
11 | coverage/
12 |
13 | # Environment variables
14 | .env
15 | .env.local
16 | .env.development
17 | .env.test
18 | .env.production
19 |
20 | # Logs
21 | logs
22 | *.log
23 | npm-debug.log*
24 | yarn-debug.log*
25 | yarn-error.log*
26 |
27 | # Editor directories and files
28 | .idea/
29 | .vscode/
30 | .DS_Store
31 | *.suo
32 | *.ntvs*
33 | *.njsproj
34 | *.sln
35 | *.sw?
36 |
37 | # Temporary files
38 | .tmp/
39 | .temp/
40 |
41 | mcp-atlassian-server-bundle*.zip
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Atlassian Server (by phuc-nt)
2 |
3 | <p align="center">
4 | <img src="assets/atlassian_logo_icon.png" alt="Atlassian Logo" width="120" />
5 | </p>
6 |
7 | [](https://github.com/phuc-nt/mcp-atlassian-server)
8 | [](https://smithery.ai/server/@phuc-nt/mcp-atlassian-server)
9 |
10 | ## What's New in Version 2.1.1 🚀
11 |
12 | - Refactored the entire codebase to standardize resource/tool structure, completely removed the content-metadata resource, and merged metadata into the page resource.
13 | - New developer guide: anyone can now easily extend and maintain the codebase.
14 | - Ensured compatibility with the latest MCP SDK, improved security, scalability, and maintainability.
15 | - Updated `docs/introduction/resources-and-tools.md` to remove all references to content-metadata.
16 |
17 | 👉 **See the full [CHANGELOG](./CHANGELOG.md) for details.**
18 |
19 | ## What's New in Version 2.0.1 🎉
20 |
21 | **MCP Atlassian Server v2.0.1** brings a major expansion of features and capabilities!
22 |
23 | - **Updated APIs**: Now using the latest Atlassian APIs (Jira API v3, Confluence API v2)
24 | - **Expanded Features**: Grown from 21 to 48 features, including advanced Jira and Confluence capabilities
25 | - **Enhanced Board & Sprint Management**: Complete Agile/Scrum workflow support
26 | - **Advanced Confluence Features**: Page version management, attachments handling, and comment management
27 | - **Improved Resource Registration**: Fixed duplicate resource registration issues for a more stable experience
28 | - **Documentation Update**: New comprehensive documentation series explaining MCP architecture, resource/tool development
29 |
30 | For full details on all changes, improvements, and fixes, see the [CHANGELOG](./CHANGELOG.md).
31 |
32 | ## Introduction
33 |
34 | **MCP Atlassian Server (by phuc-nt)** is a Model Context Protocol (MCP) server that connects AI agents like Cline, Claude Desktop, or Cursor to Atlassian Jira and Confluence, enabling them to query data and perform actions through a standardized interface.
35 |
36 | > **Note:** This server is primarily designed and optimized for use with Cline, though it follows the MCP standard and can work with other MCP-compatible clients.
37 |
38 | 
39 |
40 | - **Key Features:**
41 | - Connect AI agents to Atlassian Jira and Confluence
42 | - Support both Resources (read-only) and Tools (actions/mutations)
43 | - Easy integration with Cline through MCP Marketplace
44 | - Local-first design for personal development environments
45 | - Optimized integration with Cline AI assistant
46 |
47 | ## The Why Behind This Project
48 |
49 | As a developer working daily with Jira and Confluence, I found myself spending significant time navigating these tools. While they're powerful, I longed for a simpler way to interact with them without constantly context-switching during deep work.
50 |
51 | The emergence of AI Agents and the Model Context Protocol (MCP) presented the perfect opportunity. I immediately saw the potential to connect Jira and Confluence (with plans for Slack, GitHub, Calendar, and more) to my AI workflows.
52 |
53 | This project began as a learning journey into MCP and AI Agents, but I hope it evolves into something truly useful for individuals and organizations who interact with Atlassian tools daily.
54 |
55 | ## System Architecture
56 |
57 | ```mermaid
58 | graph TD
59 | AI[Cline AI Assistant] <--> MCP[MCP Atlassian Server]
60 | MCP <--> JiraAPI[Jira API]
61 | MCP <--> ConfAPI[Confluence API]
62 |
63 | subgraph "MCP Server"
64 | Resources[Resources - Read Only]
65 | Tools[Tools - Actions]
66 | end
67 |
68 | Resources --> JiraRes[Jira Resources<br/>issues, projects, users]
69 | Resources --> ConfRes[Confluence Resources<br/>spaces, pages]
70 | Tools --> JiraTools[Jira Tools<br/>create, update, transition]
71 | Tools --> ConfTools[Confluence Tools<br/>create page, comment]
72 | ```
73 |
74 | ## Installation & Setup
75 |
76 | For detailed installation and setup instructions, please refer to our [installation guide for AI assistants](./llms-install.md). This guide is specially formatted for AI/LLM assistants like Cline to read and automatically set up the MCP Atlassian Server.
77 |
78 | > **Note for Cline users**: The installation guide (llms-install.md) is optimized for Cline AI to understand and execute. You can simply ask Cline to "Install MCP Atlassian Server (by phuc-nt)" and it will be able to parse the instructions and help you set up everything step-by-step.
79 |
80 | The guide includes:
81 | - Prerequisites and system requirements
82 | - Step-by-step setup for Node.js environments
83 | - Configuring Cline AI assistant to connect with Atlassian
84 | - Getting and setting up Atlassian API tokens
85 | - Security recommendations and best practices
86 |
87 | ### Installing via Smithery
88 |
89 | To install Atlassian Integration Server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@phuc-nt/mcp-atlassian-server):
90 |
91 | ```bash
92 | npx -y @smithery/cli install @phuc-nt/mcp-atlassian-server --client claude
93 | ```
94 |
95 | ## Feature Overview
96 |
97 | MCP Atlassian Server enables AI assistants (like Cline, Claude Desktop, Cursor...) to access and manage Jira & Confluence with a full set of features, grouped for clarity:
98 |
99 | ### Jira
100 |
101 | - **Issue Management**
102 | - View, search, and filter issues
103 | - Create, update, transition, and assign issues
104 | - Add issues to backlog or sprint, rank issues
105 |
106 | - **Project Management**
107 | - View project list, project details, and project roles
108 |
109 | - **Board & Sprint Management**
110 | - View boards, board configuration, issues and sprints on boards
111 | - Create, start, and close sprints
112 |
113 | - **Filter Management**
114 | - View, create, update, and delete filters
115 |
116 | - **Dashboard & Gadget Management**
117 | - View dashboards and gadgets
118 | - Create and update dashboards
119 | - Add or remove gadgets on dashboards
120 |
121 | - **User Management**
122 | - View user details, assignable users, and users by project role
123 |
124 | ### Confluence
125 |
126 | - **Space Management**
127 | - View space list, space details, and pages in a space
128 |
129 | - **Page Management**
130 | - View, search, and get details of pages, child pages, ancestors, attachments, and version history
131 | - Create, update, rename, and delete pages
132 |
133 | - **Comment Management**
134 | - View, add, update, and delete comments on pages
135 |
136 |
137 | > For a full technical breakdown of all features, resources, and tools, see:
138 | > [docs/introduction/resources-and-tools.md](./docs/introduction/resources-and-tools.md)
139 |
140 | ---
141 |
142 | ## Request Flow
143 |
144 | ```mermaid
145 | sequenceDiagram
146 | participant User
147 | participant Cline as Cline AI
148 | participant MCP as MCP Server
149 | participant Atlassian as Atlassian API
150 |
151 | User->>Cline: "Find all my assigned issues"
152 | Cline->>MCP: Request jira://issues
153 | MCP->>Atlassian: API Request with Auth
154 | Atlassian->>MCP: JSON Response
155 | MCP->>Cline: Formatted MCP Resource
156 | Cline->>User: "I found these issues..."
157 |
158 | User->>Cline: "Create new issue about login bug"
159 | Cline->>MCP: Call createIssue Tool
160 | MCP->>Atlassian: POST /rest/api/3/issue
161 | Atlassian->>MCP: Created Issue Data
162 | MCP->>Cline: Success Response
163 | Cline->>User: "Created issue DEMO-123"
164 | ```
165 |
166 | ## Security Note
167 |
168 | - Your API token inherits all permissions of the user that created it
169 | - Never share your token with a non-trusted party
170 | - Be cautious when asking LLMs to analyze config files containing your token
171 | - See detailed security guidelines in [llms-install.md](./llms-install.md#security-warning-when-using-llms)
172 |
173 | ## Contribute & Support
174 |
175 | - Contribute by opening Pull Requests or Issues on GitHub.
176 | - Join the MCP/Cline community for additional support.
```
--------------------------------------------------------------------------------
/src/utils/jira-tool-api.ts:
--------------------------------------------------------------------------------
```typescript
1 | export * from './jira-tool-api-v3.js';
2 | export * from './jira-tool-api-agile.js';
```
--------------------------------------------------------------------------------
/dev_mcp-atlassian-test-client/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "outDir": "./dist",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "NodeNext",
5 | "moduleResolution": "NodeNext",
6 | "outDir": "./dist",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true,
12 | "resolveJsonModule": true
13 | },
14 | "include": ["src/**/*"],
15 | "exclude": ["node_modules", "**/*.test.ts"]
16 | }
```
--------------------------------------------------------------------------------
/dev_mcp-atlassian-test-client/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "dev_mcp-atlassian-test-client",
3 | "version": "1.0.0",
4 | "description": "Test client for MCP Atlassian server",
5 | "type": "module",
6 | "main": "dist/tool-test.js",
7 | "scripts": {
8 | "build": "tsc",
9 | "start": "node dist/index.js",
10 | "dev": "ts-node --esm src/index.ts"
11 | },
12 | "keywords": [],
13 | "author": "",
14 | "license": "ISC",
15 | "dependencies": {
16 | "@modelcontextprotocol/sdk": "^1.11.0"
17 | },
18 | "devDependencies": {
19 | "@types/node": "^22.15.2",
20 | "ts-node": "^10.9.2",
21 | "typescript": "^5.8.3"
22 | }
23 | }
24 |
```
--------------------------------------------------------------------------------
/src/resources/confluence/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2 | import { registerSpaceResources } from './spaces.js';
3 | import { registerPageResources } from './pages.js';
4 | import { Logger } from '../../utils/logger.js';
5 |
6 | const logger = Logger.getLogger('ConfluenceResources');
7 |
8 | /**
9 | * Register all Confluence resources with MCP Server
10 | * @param server MCP Server instance
11 | */
12 | export function registerConfluenceResources(server: McpServer) {
13 | logger.info('Registering Confluence resources...');
14 |
15 | // Register specific Confluence resources
16 | registerSpaceResources(server);
17 | registerPageResources(server);
18 |
19 | logger.info('Confluence resources registered successfully');
20 | }
21 |
```
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
```yaml
1 | version: '3.8'
2 |
3 | services:
4 | # MCP Server với STDIO transport
5 | mcp-atlassian:
6 | build:
7 | context: .
8 | dockerfile: Dockerfile
9 | image: mcp-atlassian-server:latest
10 | container_name: mcp-atlassian
11 | environment:
12 | - ATLASSIAN_SITE_NAME=${ATLASSIAN_SITE_NAME}
13 | - ATLASSIAN_USER_EMAIL=${ATLASSIAN_USER_EMAIL}
14 | - ATLASSIAN_API_TOKEN=${ATLASSIAN_API_TOKEN}
15 | - MCP_SERVER_NAME=${MCP_SERVER_NAME:-mcp-atlassian-integration}
16 | - MCP_SERVER_VERSION=${MCP_SERVER_VERSION:-1.0.0}
17 | stdin_open: true # Cần thiết cho STDIO transport
18 | tty: true # Cần thiết cho STDIO transport
19 | volumes:
20 | - ./.env:/app/.env:ro
21 | restart: unless-stopped
```
--------------------------------------------------------------------------------
/src/resources/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2 | import { registerJiraResources } from './jira/index.js';
3 | import { registerConfluenceResources } from './confluence/index.js';
4 | import { Logger } from '../utils/logger.js';
5 |
6 | const logger = Logger.getLogger('MCPResources');
7 |
8 | /**
9 | * Register all resources (Jira and Confluence) with MCP Server
10 | * @param server MCP Server instance
11 | */
12 | export function registerAllResources(server: McpServer) {
13 | logger.info('Registering all MCP resources...');
14 |
15 | // Register all Jira resources
16 | registerJiraResources(server);
17 |
18 | // Register all Confluence resources
19 | registerConfluenceResources(server);
20 |
21 | logger.info('All MCP resources registered successfully');
22 | }
23 |
```
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
```javascript
1 | /** @type {import('ts-jest').JestConfigWithTsJest} */
2 | export default {
3 | preset: 'ts-jest',
4 | testEnvironment: 'node',
5 | extensionsToTreatAsEsm: ['.ts'],
6 | transform: {
7 | '^.+\\.tsx?$': [
8 | 'ts-jest',
9 | {
10 | useESM: true,
11 | },
12 | ],
13 | },
14 | moduleNameMapper: {
15 | '^(\\.{1,2}/.*)\\.js$': '$1',
16 | },
17 | collectCoverage: true,
18 | coverageDirectory: 'coverage',
19 | collectCoverageFrom: [
20 | 'src/**/*.ts',
21 | '!src/tests/**/*.ts',
22 | ],
23 | testMatch: [
24 | '**/src/tests/unit/**/*.test.ts',
25 | '**/src/tests/integration/**/*.test.ts',
26 | '**/src/tests/e2e/**/*.test.ts'
27 | ],
28 | testPathIgnorePatterns: ['/node_modules/'],
29 | verbose: true,
30 | setupFilesAfterEnv: ['<rootDir>/src/tests/setup.ts'],
31 | globals: {
32 | 'ts-jest': {
33 | isolatedModules: true
34 | }
35 | }
36 | };
```
--------------------------------------------------------------------------------
/src/resources/jira/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2 | import { registerIssueResources } from './issues.js';
3 | import { registerProjectResources } from './projects.js';
4 | import { registerUserResources } from './users.js';
5 | import { registerFilterResources } from './filters.js';
6 | import { registerBoardResources } from './boards.js';
7 | import { registerSprintResources } from './sprints.js';
8 | import { registerDashboardResources } from './dashboards.js';
9 | import { registerGetJiraGadgetsResource } from '../../tools/jira/get-gadgets.js';
10 | import { Logger } from '../../utils/logger.js';
11 |
12 | const logger = Logger.getLogger('JiraResources');
13 |
14 | /**
15 | * Register all Jira resources with MCP Server
16 | * @param server MCP Server instance
17 | */
18 | export function registerJiraResources(server: McpServer) {
19 | logger.info('Registering Jira resources...');
20 |
21 | // Register specific Jira resources
22 | registerIssueResources(server);
23 | registerProjectResources(server);
24 | registerUserResources(server);
25 | registerFilterResources(server);
26 | registerBoardResources(server);
27 | registerSprintResources(server);
28 | registerDashboardResources(server);
29 | registerGetJiraGadgetsResource(server);
30 |
31 | logger.info('Jira resources registered successfully');
32 | }
33 |
```
--------------------------------------------------------------------------------
/src/utils/mcp-core.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Core interfaces and functions for MCP responses
3 | * This module provides the foundation for all MCP responses
4 | */
5 |
6 | /**
7 | * Standard MCP response interface
8 | */
9 | export interface McpResponse<T = any> {
10 | contents: Array<McpContent>;
11 | isError?: boolean;
12 | data?: T;
13 | [key: string]: unknown;
14 | }
15 |
16 | /**
17 | * MCP content types
18 | */
19 | export type McpContent =
20 | { uri: string; mimeType: string; text: string; [key: string]: unknown }
21 | | { uri: string; mimeType: string; blob: string; [key: string]: unknown };
22 |
23 | /**
24 | * Create a standard response with JSON content
25 | */
26 | export function createJsonResponse<T>(uri: string, data: T, mimeType = 'application/json'): McpResponse<T> {
27 | return {
28 | contents: [
29 | {
30 | uri,
31 | mimeType,
32 | text: JSON.stringify(data)
33 | }
34 | ],
35 | data
36 | };
37 | }
38 |
39 | /**
40 | * Create a standard success response
41 | */
42 | export function createSuccessResponse(uri: string, message: string, data?: any): McpResponse {
43 | return createJsonResponse(uri, {
44 | success: true,
45 | message,
46 | ...(data && { data })
47 | });
48 | }
49 |
50 | /**
51 | * Create a standard error response
52 | */
53 | export function createErrorResponse(uri: string, message: string, details?: any): McpResponse {
54 | return {
55 | contents: [
56 | {
57 | uri,
58 | mimeType: 'application/json',
59 | text: JSON.stringify({
60 | success: false,
61 | message,
62 | ...(details && { details })
63 | })
64 | }
65 | ],
66 | isError: true
67 | };
68 | }
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "@phuc-nt/mcp-atlassian-server",
3 | "version": "2.1.1",
4 | "description": "MCP Server for interacting with Atlassian Jira and Confluence",
5 | "type": "module",
6 | "main": "dist/index.js",
7 | "files": [
8 | "dist",
9 | "README.md",
10 | "LICENSE",
11 | "assets"
12 | ],
13 | "bin": {
14 | "mcp-atlassian-server": "dist/index.js"
15 | },
16 | "scripts": {
17 | "test": "jest",
18 | "build": "tsc",
19 | "start": "node dist/index.js",
20 | "dev": "nodemon --exec ts-node --esm src/index.ts",
21 | "prepublishOnly": "npm run build"
22 | },
23 | "keywords": [
24 | "mcp",
25 | "model",
26 | "context",
27 | "protocol",
28 | "atlassian",
29 | "jira",
30 | "confluence"
31 | ],
32 | "author": "Phuc Nguyen",
33 | "license": "MIT",
34 | "dependencies": {
35 | "@modelcontextprotocol/sdk": "^1.11.0",
36 | "axios": "^1.6.2",
37 | "axios-retry": "^4.5.0",
38 | "cross-fetch": "^4.1.0",
39 | "dotenv": "^16.4.1",
40 | "jira.js": "^3.0.0",
41 | "zod": "^3.22.4"
42 | },
43 | "devDependencies": {
44 | "@types/jest": "^29.5.12",
45 | "@types/node": "^20.11.13",
46 | "jest": "^29.7.0",
47 | "nodemon": "^3.0.3",
48 | "ts-jest": "^29.1.2",
49 | "ts-node": "^10.9.2",
50 | "typescript": "^5.3.3"
51 | },
52 | "repository": {
53 | "type": "git",
54 | "url": "git+https://github.com/phuc-nt/mcp-atlassian-server.git"
55 | },
56 | "homepage": "https://github.com/phuc-nt/mcp-atlassian-server#readme",
57 | "bugs": {
58 | "url": "https://github.com/phuc-nt/mcp-atlassian-server/issues"
59 | },
60 | "engines": {
61 | "node": ">=16.0.0"
62 | },
63 | "publishConfig": {
64 | "access": "public"
65 | }
66 | }
67 |
```
--------------------------------------------------------------------------------
/src/tools/jira/get-gadgets.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
2 | import { gadgetListSchema } from '../../schemas/jira.js';
3 | import { getJiraAvailableGadgets } from '../../utils/jira-tool-api-v3.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Config, Resources } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:getGadgets');
8 |
9 | export const registerGetJiraGadgetsResource = (server: McpServer) => {
10 | server.resource(
11 | 'jira-gadgets-list',
12 | new ResourceTemplate('jira://gadgets', {
13 | list: async (_extra: any) => ({
14 | resources: [
15 | {
16 | uri: 'jira://gadgets',
17 | name: 'Jira Gadgets',
18 | description: 'List all available Jira gadgets for dashboard.',
19 | mimeType: 'application/json'
20 | }
21 | ]
22 | })
23 | }),
24 | async (uri: string | URL, params: Record<string, any>, extra: any) => {
25 | try {
26 | // Get config from context or environment
27 | const config = Config.getConfigFromContextOrEnv(extra?.context);
28 | const uriStr = typeof uri === 'string' ? uri : uri.href;
29 | const gadgets = await getJiraAvailableGadgets(config);
30 | return Resources.createStandardResource(
31 | uriStr,
32 | gadgets,
33 | 'gadgets',
34 | gadgetListSchema,
35 | gadgets.length,
36 | gadgets.length,
37 | 0,
38 | `${config.baseUrl}/jira/dashboards`
39 | );
40 | } catch (error) {
41 | logger.error('Error in getJiraAvailableGadgets:', error);
42 | throw error;
43 | }
44 | }
45 | );
46 | };
```
--------------------------------------------------------------------------------
/smithery.yaml:
--------------------------------------------------------------------------------
```yaml
1 | # Smithery configuration file: https://smithery.ai/docs/config#smitheryyaml
2 |
3 | startCommand:
4 | type: stdio
5 | configSchema:
6 | # JSON Schema defining the configuration options for the MCP.
7 | type: object
8 | required:
9 | - atlassianSiteName
10 | - atlassianUserEmail
11 | - atlassianApiToken
12 | properties:
13 | atlassianSiteName:
14 | type: string
15 | description: Your Atlassian site name or URL (e.g., example.atlassian.net or
16 | https://example.atlassian.net)
17 | atlassianUserEmail:
18 | type: string
19 | description: Email address associated with your Atlassian account
20 | atlassianApiToken:
21 | type: string
22 | description: API token for Atlassian authentication
23 | mcpServerName:
24 | type: string
25 | default: mcp-atlassian-integration
26 | description: Optional custom name for the MCP server
27 | mcpServerVersion:
28 | type: string
29 | default: 1.0.0
30 | description: Optional custom version for the MCP server
31 | commandFunction:
32 | # A JS function that produces the CLI command based on the given config to start the MCP on stdio.
33 | |-
34 | (config) => ({
35 | command: 'node',
36 | args: ['dist/index.js'],
37 | env: {
38 | ATLASSIAN_SITE_NAME: config.atlassianSiteName,
39 | ATLASSIAN_USER_EMAIL: config.atlassianUserEmail,
40 | ATLASSIAN_API_TOKEN: config.atlassianApiToken,
41 | MCP_SERVER_NAME: config.mcpServerName,
42 | MCP_SERVER_VERSION: config.mcpServerVersion
43 | }
44 | })
45 | exampleConfig:
46 | atlassianSiteName: example.atlassian.net
47 | atlassianUserEmail: [email protected]
48 | atlassianApiToken: your-api-token
49 | mcpServerName: my-atlassian-mcp
50 | mcpServerVersion: 1.2.3
51 |
```
--------------------------------------------------------------------------------
/src/tools/jira/delete-filter.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { deleteFilter } from '../../utils/jira-tool-api-v3.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | // Initialize logger
8 | const logger = Logger.getLogger('JiraTools:deleteFilter');
9 |
10 | // Input parameter schema
11 | export const deleteFilterSchema = z.object({
12 | filterId: z.string().describe('Filter ID to delete')
13 | });
14 |
15 | type DeleteFilterParams = z.infer<typeof deleteFilterSchema>;
16 |
17 | async function deleteFilterToolImpl(params: DeleteFilterParams, context: any) {
18 | const config = Config.getConfigFromContextOrEnv(context);
19 | logger.info(`Deleting filter with ID: ${params.filterId}`);
20 | await deleteFilter(config, params.filterId);
21 | return {
22 | success: true,
23 | filterId: params.filterId
24 | };
25 | }
26 |
27 | // Register the tool with MCP Server
28 | export const registerDeleteFilterTool = (server: McpServer) => {
29 | server.tool(
30 | 'deleteFilter',
31 | 'Delete a filter in Jira',
32 | deleteFilterSchema.shape,
33 | async (params: DeleteFilterParams, context: Record<string, any>) => {
34 | try {
35 | const result = await deleteFilterToolImpl(params, context);
36 | return {
37 | content: [
38 | {
39 | type: 'text',
40 | text: JSON.stringify(result)
41 | }
42 | ]
43 | };
44 | } catch (error) {
45 | logger.error('Error in deleteFilter:', error);
46 | return {
47 | content: [
48 | {
49 | type: 'text',
50 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
51 | }
52 | ],
53 | isError: true
54 | };
55 | }
56 | }
57 | );
58 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/close-sprint.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { closeSprint } from '../../utils/jira-tool-api-agile.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:closeSprint');
8 |
9 | export const closeSprintSchema = z.object({
10 | sprintId: z.string().describe('Sprint ID'),
11 | completeDate: z.string().optional().describe('Complete date (ISO 8601, optional, e.g. 2025-05-10T12:45:00.000+07:00)')
12 | });
13 |
14 | type CloseSprintParams = z.infer<typeof closeSprintSchema>;
15 |
16 | async function closeSprintToolImpl(params: CloseSprintParams, context: any) {
17 | const config = Config.getConfigFromContextOrEnv(context);
18 | const { sprintId, ...options } = params;
19 | const result = await closeSprint(config, sprintId, options);
20 | return {
21 | success: true,
22 | sprintId,
23 | ...options,
24 | result
25 | };
26 | }
27 |
28 | export const registerCloseSprintTool = (server: McpServer) => {
29 | server.tool(
30 | 'closeSprint',
31 | 'Close a Jira sprint',
32 | closeSprintSchema.shape,
33 | async (params: CloseSprintParams, context: Record<string, any>) => {
34 | try {
35 | const result = await closeSprintToolImpl(params, context);
36 | return {
37 | content: [
38 | {
39 | type: 'text',
40 | text: JSON.stringify(result)
41 | }
42 | ]
43 | };
44 | } catch (error) {
45 | logger.error('Error in closeSprint:', error);
46 | return {
47 | content: [
48 | {
49 | type: 'text',
50 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
51 | }
52 | ],
53 | isError: true
54 | };
55 | }
56 | }
57 | );
58 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/create-dashboard.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { createDashboard } from '../../utils/jira-tool-api-v3.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:createDashboard');
8 |
9 | export const createDashboardSchema = z.object({
10 | name: z.string().describe('Dashboard name'),
11 | description: z.string().optional().describe('Dashboard description'),
12 | sharePermissions: z.array(z.any()).optional().describe('Share permissions array')
13 | });
14 |
15 | type CreateDashboardParams = z.infer<typeof createDashboardSchema>;
16 |
17 | async function createDashboardToolImpl(params: CreateDashboardParams, context: any) {
18 | const config = Config.getConfigFromContextOrEnv(context);
19 | const result = await createDashboard(config, params);
20 | return {
21 | success: true,
22 | ...result
23 | };
24 | }
25 |
26 | export const registerCreateDashboardTool = (server: McpServer) => {
27 | server.tool(
28 | 'createDashboard',
29 | 'Create a new Jira dashboard',
30 | createDashboardSchema.shape,
31 | async (params: CreateDashboardParams, context: Record<string, any>) => {
32 | try {
33 | const result = await createDashboardToolImpl(params, context);
34 | return {
35 | content: [
36 | {
37 | type: 'text',
38 | text: JSON.stringify(result)
39 | }
40 | ]
41 | };
42 | } catch (error) {
43 | logger.error('Error in createDashboard:', error);
44 | return {
45 | content: [
46 | {
47 | type: 'text',
48 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
49 | }
50 | ],
51 | isError: true
52 | };
53 | }
54 | }
55 | );
56 | };
```
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
```markdown
1 | # CHANGELOG
2 |
3 | ## [2.1.1] - 2025-05-17
4 |
5 | ### 📝 Patch Release
6 | - Documentation and metadata updates only. No code changes.
7 |
8 | ## [2.1.0] - 2025-05-17
9 |
10 | ### ✨ Refactor & Standardization
11 | - Refactored the entire codebase to standardize resource/tool structure
12 | - Completely removed the content-metadata resource, merged metadata into the page resource
13 | - Updated and standardized developer documentation for easier extension and maintenance
14 | - Ensured compatibility with the latest MCP SDK, improved security, scalability, and maintainability
15 | - Updated `docs/introduction/resources-and-tools.md` to remove all references to content-metadata
16 |
17 | ### 🔧 Bug Fixes
18 | - Fixed duplicate resource registration issues
19 | - Improved resource management and registration process
20 | - Resolved issues with conflicting resource patterns
21 |
22 | ## [2.0.0] - 2025-05-11
23 |
24 | ### ✨ Improvements
25 | - Updated all APIs to latest versions (Jira API v3, Confluence API v2)
26 | - Improved documentation and README structure
27 | - Reorganized resources and tools into logical groups
28 |
29 | ### 🎉 New Features
30 | - **Jira Board & Sprint:** Management of boards, sprints, and issues for Agile/Scrum workflows
31 | - **Jira Dashboard & Gadgets:** Create/update dashboards, add/remove gadgets
32 | - **Jira Filters:** Create, view, update, delete search filters for issues
33 | - **Advanced Confluence Pages:** Version management, attachments, page deletion
34 | - **Confluence Comments:** Update and delete comments
35 | - Expanded from 21 to 48 features, including numerous new tools for both Jira and Confluence
36 |
37 | ### 🔧 Bug Fixes
38 | - Fixed issues with Jira dashboard and gadget tools/resources
39 | - Resolved problems with jira://users resource
40 | - Improved error handling and messaging
41 | - Fixed compatibility issues between API versions
42 |
43 | ### 🔄 Code Changes
44 | - Restructured codebase for easier future expansion
45 | - Improved feature implementation workflow
```
--------------------------------------------------------------------------------
/src/tools/jira/start-sprint.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { startSprint } from '../../utils/jira-tool-api-agile.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:startSprint');
8 |
9 | export const startSprintSchema = z.object({
10 | sprintId: z.string().describe('Sprint ID'),
11 | startDate: z.string().describe('Start date (ISO 8601)'),
12 | endDate: z.string().describe('End date (ISO 8601)'),
13 | goal: z.string().optional().describe('Sprint goal')
14 | });
15 |
16 | type StartSprintParams = z.infer<typeof startSprintSchema>;
17 |
18 | async function startSprintToolImpl(params: StartSprintParams, context: any) {
19 | const config = Config.getConfigFromContextOrEnv(context);
20 | const { sprintId, startDate, endDate, goal } = params;
21 | const result = await startSprint(config, sprintId, startDate, endDate, goal);
22 | return {
23 | success: true,
24 | sprintId,
25 | startDate,
26 | endDate,
27 | goal: goal || null,
28 | result
29 | };
30 | }
31 |
32 | export const registerStartSprintTool = (server: McpServer) => {
33 | server.tool(
34 | 'startSprint',
35 | 'Start a Jira sprint',
36 | startSprintSchema.shape,
37 | async (params: StartSprintParams, context: Record<string, any>) => {
38 | try {
39 | const result = await startSprintToolImpl(params, context);
40 | return {
41 | content: [
42 | {
43 | type: 'text',
44 | text: JSON.stringify(result)
45 | }
46 | ]
47 | };
48 | } catch (error) {
49 | logger.error('Error in startSprint:', error);
50 | return {
51 | content: [
52 | {
53 | type: 'text',
54 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
55 | }
56 | ],
57 | isError: true
58 | };
59 | }
60 | }
61 | );
62 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/remove-gadget-from-dashboard.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { removeGadgetFromDashboard } from '../../utils/jira-tool-api-v3.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:removeGadgetFromDashboard');
8 |
9 | export const removeGadgetFromDashboardSchema = z.object({
10 | dashboardId: z.string().describe('Dashboard ID'),
11 | gadgetId: z.string().describe('Gadget ID')
12 | });
13 |
14 | type RemoveGadgetFromDashboardParams = z.infer<typeof removeGadgetFromDashboardSchema>;
15 |
16 | async function removeGadgetFromDashboardToolImpl(params: RemoveGadgetFromDashboardParams, context: any) {
17 | const config = Config.getConfigFromContextOrEnv(context);
18 | const { dashboardId, gadgetId } = params;
19 | const result = await removeGadgetFromDashboard(config, dashboardId, gadgetId);
20 | return {
21 | success: true,
22 | dashboardId,
23 | gadgetId,
24 | result
25 | };
26 | }
27 |
28 | export const registerRemoveGadgetFromDashboardTool = (server: McpServer) => {
29 | server.tool(
30 | 'removeGadgetFromDashboard',
31 | 'Remove gadget from Jira dashboard',
32 | removeGadgetFromDashboardSchema.shape,
33 | async (params: RemoveGadgetFromDashboardParams, context: Record<string, any>) => {
34 | try {
35 | const result = await removeGadgetFromDashboardToolImpl(params, context);
36 | return {
37 | content: [
38 | {
39 | type: 'text',
40 | text: JSON.stringify(result)
41 | }
42 | ]
43 | };
44 | } catch (error) {
45 | logger.error('Error in removeGadgetFromDashboard:', error);
46 | return {
47 | content: [
48 | {
49 | type: 'text',
50 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
51 | }
52 | ],
53 | isError: true
54 | };
55 | }
56 | }
57 | );
58 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/update-dashboard.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { updateDashboard } from '../../utils/jira-tool-api-v3.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:updateDashboard');
8 |
9 | export const updateDashboardSchema = z.object({
10 | dashboardId: z.string().describe('Dashboard ID'),
11 | name: z.string().optional().describe('Dashboard name'),
12 | description: z.string().optional().describe('Dashboard description'),
13 | sharePermissions: z.array(z.any()).optional().describe('Share permissions array')
14 | });
15 |
16 | type UpdateDashboardParams = z.infer<typeof updateDashboardSchema>;
17 |
18 | async function updateDashboardToolImpl(params: UpdateDashboardParams, context: any) {
19 | const config = Config.getConfigFromContextOrEnv(context);
20 | const { dashboardId, ...data } = params;
21 | const result = await updateDashboard(config, dashboardId, data);
22 | return {
23 | success: true,
24 | dashboardId,
25 | ...result
26 | };
27 | }
28 |
29 | export const registerUpdateDashboardTool = (server: McpServer) => {
30 | server.tool(
31 | 'updateDashboard',
32 | 'Update a Jira dashboard',
33 | updateDashboardSchema.shape,
34 | async (params: UpdateDashboardParams, context: Record<string, any>) => {
35 | try {
36 | const result = await updateDashboardToolImpl(params, context);
37 | return {
38 | content: [
39 | {
40 | type: 'text',
41 | text: JSON.stringify(result)
42 | }
43 | ]
44 | };
45 | } catch (error) {
46 | logger.error('Error in updateDashboard:', error);
47 | return {
48 | content: [
49 | {
50 | type: 'text',
51 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
52 | }
53 | ],
54 | isError: true
55 | };
56 | }
57 | }
58 | );
59 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/add-issue-to-sprint.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { addIssueToSprint } from '../../utils/jira-tool-api-agile.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:addIssueToSprint');
8 |
9 | export const addIssueToSprintSchema = z.object({
10 | sprintId: z.string().describe('Target sprint ID (must be future or active)'),
11 | issueKeys: z.array(z.string()).min(1).max(50).describe('List of issue keys to move to the sprint (max 50)')
12 | });
13 |
14 | type AddIssueToSprintParams = z.infer<typeof addIssueToSprintSchema>;
15 |
16 | async function addIssueToSprintToolImpl(params: AddIssueToSprintParams, context: any) {
17 | const config = Config.getConfigFromContextOrEnv(context);
18 | const { sprintId, issueKeys } = params;
19 | const result = await addIssueToSprint(config, sprintId, issueKeys);
20 | return {
21 | success: true,
22 | sprintId,
23 | issueKeys,
24 | result
25 | };
26 | }
27 |
28 | export const registerAddIssueToSprintTool = (server: McpServer) => {
29 | server.tool(
30 | 'addIssueToSprint',
31 | 'Add issues to a Jira sprint (POST /rest/agile/1.0/sprint/{sprintId}/issue)',
32 | addIssueToSprintSchema.shape,
33 | async (params: AddIssueToSprintParams, context: Record<string, any>) => {
34 | try {
35 | const result = await addIssueToSprintToolImpl(params, context);
36 | return {
37 | content: [
38 | {
39 | type: 'text',
40 | text: JSON.stringify(result)
41 | }
42 | ]
43 | };
44 | } catch (error) {
45 | logger.error('Error in addIssueToSprint:', error);
46 | return {
47 | content: [
48 | {
49 | type: 'text',
50 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
51 | }
52 | ],
53 | isError: true
54 | };
55 | }
56 | }
57 | );
58 | };
```
--------------------------------------------------------------------------------
/docs/dev-guide/marketplace-publish-application-template.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Server Submission Template
2 |
3 | ## [Server Submission]: {SERVER_NAME}
4 |
5 | **GitHub Repo URL:**
6 | {FULL_GITHUB_REPOSITORY_URL}
7 |
8 | **Logo Image:**
9 | {ATTACH_400x400_PNG_LOGO_HERE}
10 |
11 | **Reason for Addition:**
12 | {1-2 SENTENCES EXPLAINING WHAT THIS SERVER DOES AND WHY IT'S VALUABLE}
13 |
14 | ### Key Features
15 | - {FEATURE_1}
16 | - {FEATURE_2}
17 | - {FEATURE_3}
18 | - {FEATURE_4}
19 | - {FEATURE_5}
20 |
21 | ### Use Cases
22 | - {USE_CASE_1}
23 | - {USE_CASE_2}
24 | - {USE_CASE_3}
25 | - {USE_CASE_4}
26 |
27 | ### Installation
28 | {1-2 SENTENCES ABOUT INSTALLATION PROCESS AND DOCUMENTATION}
29 |
30 | ### Requirements
31 | - {REQUIREMENT_1}
32 | - {REQUIREMENT_2}
33 | - {REQUIREMENT_3}
34 | - {REQUIREMENT_4}
35 |
36 | {OPTIONAL_ADDITIONAL_NOTES}
37 |
38 | ---
39 |
40 | ## Example Filled Template
41 |
42 | ## [Server Submission]: MCP Atlassian Server
43 |
44 | **GitHub Repo URL:**
45 | https://github.com/username/mcp-atlassian-server
46 |
47 | **Logo Image:**
48 | [Atlassian MCP Server Logo.png]
49 |
50 | **Reason for Addition:**
51 | This MCP server connects AI assistants to Atlassian Jira & Confluence, enabling natural language interaction with project management tools. It helps users manage issues, projects, and documentation without switching contexts.
52 |
53 | ### Key Features
54 | - Connects AI assistants to Jira and Confluence Cloud
55 | - Supports both Resources (read-only data) and Tools (actions)
56 | - Local-first design for privacy and performance
57 | - Thoroughly tested with Cline
58 | - Detailed documentation for easy installation
59 |
60 | ### Use Cases
61 | - Query Jira issues, projects, and user information
62 | - Create and update issues, transition states, assign tasks
63 | - Create Confluence pages and add comments
64 | - Generate reports and summaries from Atlassian data
65 |
66 | ### Installation
67 | The server includes comprehensive installation instructions in README.md and a detailed llms-install.md specifically designed for AI-assisted installation.
68 |
69 | ### Requirements
70 | - Node.js 16+ and npm
71 | - Atlassian Cloud account with API token
72 | - Compatible with Cline and other MCP clients
73 |
74 | This server follows MCP best practices and is ready for one-click installation via Marketplace.
```
--------------------------------------------------------------------------------
/src/tools/jira/update-filter.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { Logger } from '../../utils/logger.js';
4 | import { updateFilter } from '../../utils/jira-tool-api-v3.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | // Initialize logger
8 | const logger = Logger.getLogger('JiraTools:updateFilter');
9 |
10 | // Input parameter schema
11 | export const updateFilterSchema = z.object({
12 | filterId: z.string().describe('Filter ID to update'),
13 | name: z.string().optional().describe('New filter name'),
14 | jql: z.string().optional().describe('New JQL query'),
15 | description: z.string().optional().describe('New description'),
16 | favourite: z.boolean().optional().describe('Mark as favourite')
17 | });
18 |
19 | type UpdateFilterParams = z.infer<typeof updateFilterSchema>;
20 |
21 | async function updateFilterToolImpl(params: UpdateFilterParams, context: any) {
22 | const config = Config.getConfigFromContextOrEnv(context);
23 | logger.info(`Updating filter with ID: ${params.filterId}`);
24 | const response = await updateFilter(config, params.filterId, params);
25 | return {
26 | id: response.id,
27 | name: response.name,
28 | self: response.self,
29 | success: true
30 | };
31 | }
32 |
33 | // Register the tool with MCP Server
34 | export const registerUpdateFilterTool = (server: McpServer) => {
35 | server.tool(
36 | 'updateFilter',
37 | 'Update an existing filter in Jira',
38 | updateFilterSchema.shape,
39 | async (params: UpdateFilterParams, context: Record<string, any>) => {
40 | try {
41 | const result = await updateFilterToolImpl(params, context);
42 | return {
43 | content: [
44 | {
45 | type: 'text',
46 | text: JSON.stringify(result)
47 | }
48 | ]
49 | };
50 | } catch (error) {
51 | logger.error('Error in updateFilter:', error);
52 | return {
53 | content: [
54 | {
55 | type: 'text',
56 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
57 | }
58 | ],
59 | isError: true
60 | };
61 | }
62 | }
63 | );
64 | };
```
--------------------------------------------------------------------------------
/docs/introduction/marketplace-submission.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Server Submission Template
2 |
3 | ## [Server Submission]: MCP Atlassian Server (by phuc-nt)
4 |
5 | **GitHub Repo URL:**
6 | https://github.com/phuc-nt/mcp-atlassian-server
7 |
8 | **Logo Image:**
9 | 
10 |
11 | **Reason for Addition:**
12 | This MCP server connects AI assistants to Atlassian Jira & Confluence, enabling natural language interaction with project management tools. It helps users manage issues, projects, and documentation without switching contexts during deep work.
13 |
14 | ### Key Features
15 | - Connects AI assistants to Atlassian Jira and Confluence
16 | - Provides both Resources (read-only data) and Tools (action endpoints)
17 | - Local-first design for personal development environments
18 | - Optimized for integration with Cline AI assistant
19 | - Comprehensive documentation for users and developers
20 |
21 | ### Use Cases
22 | - Query Jira issues, projects, and user information directly through AI
23 | - Create and update issues, transition states, assign tasks seamlessly
24 | - Create Confluence pages and add comments without context-switching
25 | - Generate reports and summaries from Atlassian data using natural language
26 | - Manage your daily Atlassian workflows through conversational interfaces
27 |
28 | ### Installation
29 | The server includes comprehensive installation instructions in README.md and a detailed llms-install.md specifically designed for AI-assisted installation. Users can simply ask Cline to "Install MCP Atlassian Server (by phuc-nt)" for one-click setup.
30 |
31 | ### Requirements
32 | - Node.js 16+ and npm
33 | - Atlassian Cloud account with API token
34 | - Compatible with Cline and other MCP-compatible clients
35 |
36 | ### Additional Notes
37 | This server follows MCP best practices and is ready for one-click installation via Marketplace. It aims to enhance developer productivity by eliminating the need to context-switch between development environments and Atlassian tools. The project is actively maintained with a clear roadmap for future enhancements.
38 |
39 | ---
40 |
41 | *This submission is intended for the Cline MCP Marketplace and follows the specified template format.*
```
--------------------------------------------------------------------------------
/src/tools/jira/create-filter.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Create Filter Tool
3 | *
4 | * This tool creates a new filter in Jira.
5 | */
6 |
7 | import { z } from 'zod';
8 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
9 | import { createFilter } from '../../utils/jira-tool-api-v3.js';
10 | import { Logger } from '../../utils/logger.js';
11 | import { Tools, Config } from '../../utils/mcp-helpers.js';
12 |
13 | // Initialize logger
14 | const logger = Logger.getLogger('JiraTools:createFilter');
15 |
16 | // Input parameter schema
17 | export const createFilterSchema = z.object({
18 | name: z.string().describe('Filter name'),
19 | jql: z.string().describe('JQL query for the filter'),
20 | description: z.string().optional().describe('Filter description'),
21 | favourite: z.boolean().optional().describe('Mark as favourite')
22 | });
23 |
24 | type CreateFilterParams = z.infer<typeof createFilterSchema>;
25 |
26 | async function createFilterToolImpl(params: CreateFilterParams, context: any) {
27 | const config = Config.getConfigFromContextOrEnv(context);
28 | logger.info(`Creating filter: ${params.name}`);
29 | const response = await createFilter(config, params.name, params.jql, params.description, params.favourite);
30 | return {
31 | id: response.id,
32 | name: response.name,
33 | self: response.self,
34 | success: true
35 | };
36 | }
37 |
38 | // Register the tool with MCP Server
39 | export const registerCreateFilterTool = (server: McpServer) => {
40 | server.tool(
41 | 'createFilter',
42 | 'Create a new filter in Jira',
43 | createFilterSchema.shape,
44 | async (params: CreateFilterParams, context: Record<string, any>) => {
45 | try {
46 | const result = await createFilterToolImpl(params, context);
47 | return {
48 | content: [
49 | {
50 | type: 'text',
51 | text: JSON.stringify(result)
52 | }
53 | ]
54 | };
55 | } catch (error) {
56 | logger.error('Error in createFilter:', error);
57 | return {
58 | content: [
59 | {
60 | type: 'text',
61 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
62 | }
63 | ],
64 | isError: true
65 | };
66 | }
67 | }
68 | );
69 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/create-sprint.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { Logger } from '../../utils/logger.js';
4 | import { createSprint } from '../../utils/jira-tool-api-agile.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | // Initialize logger
8 | const logger = Logger.getLogger('JiraTools:createSprint');
9 |
10 | // Input parameter schema
11 | export const createSprintSchema = z.object({
12 | boardId: z.string().describe('Board ID'),
13 | name: z.string().describe('Sprint name'),
14 | startDate: z.string().optional().describe('Start date (ISO format)'),
15 | endDate: z.string().optional().describe('End date (ISO format)'),
16 | goal: z.string().optional().describe('Sprint goal')
17 | });
18 |
19 | type CreateSprintParams = z.infer<typeof createSprintSchema>;
20 |
21 | async function createSprintToolImpl(params: CreateSprintParams, context: any) {
22 | const config = Config.getConfigFromContextOrEnv(context);
23 | logger.info(`Creating sprint: ${params.name} for board ${params.boardId}`);
24 | const response = await createSprint(config, params.boardId, params.name, params.startDate, params.endDate, params.goal);
25 | return {
26 | id: response.id,
27 | name: response.name,
28 | state: response.state,
29 | success: true
30 | };
31 | }
32 |
33 | // Register the tool with MCP Server
34 | export const registerCreateSprintTool = (server: McpServer) => {
35 | server.tool(
36 | 'createSprint',
37 | 'Create a new sprint in Jira',
38 | createSprintSchema.shape,
39 | async (params: CreateSprintParams, context: Record<string, any>) => {
40 | try {
41 | const result = await createSprintToolImpl(params, context);
42 | return {
43 | content: [
44 | {
45 | type: 'text',
46 | text: JSON.stringify(result)
47 | }
48 | ]
49 | };
50 | } catch (error) {
51 | logger.error('Error in createSprint:', error);
52 | return {
53 | content: [
54 | {
55 | type: 'text',
56 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
57 | }
58 | ],
59 | isError: true
60 | };
61 | }
62 | }
63 | );
64 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/rank-backlog-issues.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { rankBacklogIssues } from '../../utils/jira-tool-api-agile.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:rankBacklogIssues');
8 |
9 | export const rankBacklogIssuesSchema = z.object({
10 | boardId: z.string().describe('Board ID'),
11 | issueKeys: z.array(z.string()).describe('List of issue keys to rank'),
12 | rankBeforeIssue: z.string().optional().describe('Rank before this issue key'),
13 | rankAfterIssue: z.string().optional().describe('Rank after this issue key')
14 | });
15 |
16 | type RankBacklogIssuesParams = z.infer<typeof rankBacklogIssuesSchema>;
17 |
18 | async function rankBacklogIssuesToolImpl(params: RankBacklogIssuesParams, context: any) {
19 | const config = Config.getConfigFromContextOrEnv(context);
20 | const { boardId, issueKeys, rankBeforeIssue, rankAfterIssue } = params;
21 | const result = await rankBacklogIssues(config, boardId, issueKeys, { rankBeforeIssue, rankAfterIssue });
22 | return {
23 | success: true,
24 | boardId,
25 | issueKeys,
26 | rankBeforeIssue: rankBeforeIssue || null,
27 | rankAfterIssue: rankAfterIssue || null,
28 | result
29 | };
30 | }
31 |
32 | export const registerRankBacklogIssuesTool = (server: McpServer) => {
33 | server.tool(
34 | 'rankBacklogIssues',
35 | 'Rank issues in Jira backlog',
36 | rankBacklogIssuesSchema.shape,
37 | async (params: RankBacklogIssuesParams, context: Record<string, any>) => {
38 | try {
39 | const result = await rankBacklogIssuesToolImpl(params, context);
40 | return {
41 | content: [
42 | {
43 | type: 'text',
44 | text: JSON.stringify(result)
45 | }
46 | ]
47 | };
48 | } catch (error) {
49 | logger.error('Error in rankBacklogIssues:', error);
50 | return {
51 | content: [
52 | {
53 | type: 'text',
54 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
55 | }
56 | ],
57 | isError: true
58 | };
59 | }
60 | }
61 | );
62 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/add-issues-to-backlog.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { addIssuesToBacklog } from '../../utils/jira-tool-api-agile.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:addIssuesToBacklog');
8 |
9 | export const addIssuesToBacklogSchema = z.object({
10 | issueKeys: z.union([
11 | z.string(),
12 | z.array(z.string())
13 | ]).describe('Issue key(s) to move to backlog. Accepts a single issue key (e.g. "PROJ-123") or an array of issue keys (e.g. ["PROJ-123", "PROJ-124"]).'),
14 | boardId: z.string().optional().describe('Board ID (optional). If provided, issues will be moved to the backlog of this board.')
15 | });
16 |
17 | type AddIssuesToBacklogParams = z.infer<typeof addIssuesToBacklogSchema>;
18 |
19 | async function addIssuesToBacklogToolImpl(params: AddIssuesToBacklogParams, context: any) {
20 | const config = Config.getConfigFromContextOrEnv(context);
21 | const { boardId, issueKeys } = params;
22 | const keys = Array.isArray(issueKeys) ? issueKeys : [issueKeys];
23 | const result = await addIssuesToBacklog(config, keys, boardId);
24 | return {
25 | success: true,
26 | boardId: boardId || null,
27 | issueKeys: keys,
28 | result
29 | };
30 | }
31 |
32 | export const registerAddIssuesToBacklogTool = (server: McpServer) => {
33 | server.tool(
34 | 'addIssuesToBacklog',
35 | 'Move issue(s) to Jira backlog (POST /rest/agile/1.0/backlog/issue or /rest/agile/1.0/backlog/{boardId}/issue)',
36 | addIssuesToBacklogSchema.shape,
37 | async (params: AddIssuesToBacklogParams, context: Record<string, any>) => {
38 | try {
39 | const result = await addIssuesToBacklogToolImpl(params, context);
40 | return {
41 | content: [
42 | {
43 | type: 'text',
44 | text: JSON.stringify(result)
45 | }
46 | ]
47 | };
48 | } catch (error) {
49 | logger.error('Error in addIssuesToBacklog:', error);
50 | return {
51 | content: [
52 | {
53 | type: 'text',
54 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
55 | }
56 | ],
57 | isError: true
58 | };
59 | }
60 | }
61 | );
62 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/assign-issue.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
3 | import { assignIssue } from '../../utils/jira-tool-api-v3.js';
4 | import { ApiError } from '../../utils/error-handler.js';
5 | import { Logger } from '../../utils/logger.js';
6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7 | import { Tools, Config } from '../../utils/mcp-helpers.js';
8 |
9 | // Initialize logger
10 | const logger = Logger.getLogger('JiraTools:assignIssue');
11 |
12 | // Input parameter schema
13 | export const assignIssueSchema = z.object({
14 | issueIdOrKey: z.string().describe('ID or key of the issue (e.g., PROJ-123)'),
15 | accountId: z.string().optional().describe('Account ID of the assignee (leave blank to unassign)')
16 | });
17 |
18 | type AssignIssueParams = z.infer<typeof assignIssueSchema>;
19 |
20 | async function assignIssueToolImpl(params: AssignIssueParams, context: any) {
21 | const config: AtlassianConfig = Config.getConfigFromContextOrEnv(context);
22 | logger.info(`Assigning issue ${params.issueIdOrKey} to ${params.accountId || 'no one'}`);
23 | const result = await assignIssue(
24 | config,
25 | params.issueIdOrKey,
26 | params.accountId || null
27 | );
28 | return {
29 | issueIdOrKey: params.issueIdOrKey,
30 | success: result.success,
31 | assignee: params.accountId || null,
32 | message: params.accountId
33 | ? `Issue ${params.issueIdOrKey} assigned to user with account ID: ${params.accountId}`
34 | : `Issue ${params.issueIdOrKey} unassigned`
35 | };
36 | }
37 |
38 | export const registerAssignIssueTool = (server: McpServer) => {
39 | server.tool(
40 | 'assignIssue',
41 | 'Assign a Jira issue to a user',
42 | assignIssueSchema.shape,
43 | async (params: AssignIssueParams, context: Record<string, any>) => {
44 | try {
45 | const result = await assignIssueToolImpl(params, context);
46 | return {
47 | content: [
48 | {
49 | type: 'text',
50 | text: JSON.stringify(result)
51 | }
52 | ]
53 | };
54 | } catch (error) {
55 | return {
56 | content: [
57 | {
58 | type: 'text',
59 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
60 | }
61 | ],
62 | isError: true
63 | };
64 | }
65 | }
66 | );
67 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/transition-issue.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
3 | import { transitionIssue } from '../../utils/jira-tool-api-v3.js';
4 | import { ApiError } from '../../utils/error-handler.js';
5 | import { Logger } from '../../utils/logger.js';
6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7 | import { Tools, Config } from '../../utils/mcp-helpers.js';
8 |
9 | // Initialize logger
10 | const logger = Logger.getLogger('JiraTools:transitionIssue');
11 |
12 | // Input parameter schema
13 | export const transitionIssueSchema = z.object({
14 | issueIdOrKey: z.string().describe('ID or key of the issue (e.g., PROJ-123)'),
15 | transitionId: z.string().describe('ID of the transition to apply'),
16 | comment: z.string().optional().describe('Comment when performing the transition')
17 | });
18 |
19 | type TransitionIssueParams = z.infer<typeof transitionIssueSchema>;
20 |
21 | async function transitionIssueToolImpl(params: TransitionIssueParams, context: any) {
22 | const config: AtlassianConfig = Config.getConfigFromContextOrEnv(context);
23 | logger.info(`Transitioning issue ${params.issueIdOrKey} with transition ${params.transitionId}`);
24 | const result = await transitionIssue(
25 | config,
26 | params.issueIdOrKey,
27 | params.transitionId,
28 | params.comment
29 | );
30 | return {
31 | issueIdOrKey: params.issueIdOrKey,
32 | success: result.success,
33 | transitionId: params.transitionId,
34 | message: result.message
35 | };
36 | }
37 |
38 | // Register the tool with MCP Server
39 | export const registerTransitionIssueTool = (server: McpServer) => {
40 | server.tool(
41 | 'transitionIssue',
42 | 'Transition the status of a Jira issue',
43 | transitionIssueSchema.shape,
44 | async (params: TransitionIssueParams, context: Record<string, any>) => {
45 | try {
46 | const result = await transitionIssueToolImpl(params, context);
47 | return {
48 | content: [
49 | {
50 | type: 'text',
51 | text: JSON.stringify(result)
52 | }
53 | ]
54 | };
55 | } catch (error) {
56 | return {
57 | content: [
58 | {
59 | type: 'text',
60 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
61 | }
62 | ],
63 | isError: true
64 | };
65 | }
66 | }
67 | );
68 | };
```
--------------------------------------------------------------------------------
/start-docker.sh:
--------------------------------------------------------------------------------
```bash
1 | #!/bin/bash
2 |
3 | # Colors
4 | GREEN='\033[0;32m'
5 | YELLOW='\033[1;33m'
6 | RED='\033[0;31m'
7 | BLUE='\033[0;34m'
8 | NC='\033[0m' # No Color
9 |
10 | # Banner
11 | echo -e "${BLUE}"
12 | echo "====================================================="
13 | echo " MCP Atlassian Server Docker Manager"
14 | echo "====================================================="
15 | echo -e "${NC}"
16 |
17 | # Kiểm tra xem file .env tồn tại
18 | if [ ! -f .env ]; then
19 | echo -e "${RED}Lỗi: File .env không tồn tại.${NC}"
20 | echo "Vui lòng tạo file .env với các biến môi trường sau:"
21 | echo "ATLASSIAN_SITE_NAME=your-site.atlassian.net"
22 | echo "[email protected]"
23 | echo "ATLASSIAN_API_TOKEN=your-api-token"
24 | exit 1
25 | fi
26 |
27 | # Hiển thị menu
28 | echo -e "${YELLOW}Chọn chế độ quản lý MCP Server:${NC}"
29 | echo "1. Chạy MCP Server (với STDIO Transport)"
30 | echo "2. Dừng và xóa container"
31 | echo "3. Xem logs của container"
32 | echo "4. Hiển thị cấu hình Cline"
33 | echo "5. Thoát"
34 | echo ""
35 |
36 | read -p "Nhập lựa chọn của bạn (1-5): " choice
37 |
38 | case $choice in
39 | 1)
40 | echo -e "${GREEN}Chạy MCP Server với STDIO Transport...${NC}"
41 | docker compose up --build -d mcp-atlassian
42 | echo -e "${GREEN}MCP Server đã được khởi động với STDIO transport.${NC}"
43 | echo -e "${YELLOW}Hướng dẫn cấu hình Cline:${NC}"
44 | echo "1. Mở Cline trong VS Code"
45 | echo "2. Đi đến cấu hình MCP Servers"
46 | echo "3. Thêm cấu hình sau vào file cline_mcp_settings.json:"
47 | echo ""
48 | echo '{
49 | "mcpServers": {
50 | "atlassian-docker": {
51 | "command": "docker",
52 | "args": ["exec", "-i", "mcp-atlassian", "node", "dist/index.js"],
53 | "env": {}
54 | }
55 | }
56 | }'
57 | ;;
58 | 2)
59 | echo -e "${YELLOW}Dừng và xóa container hiện tại...${NC}"
60 | docker compose down
61 | echo -e "${GREEN}Đã dừng và xóa các container MCP Server.${NC}"
62 | ;;
63 | 3)
64 | echo -e "${YELLOW}Container đang chạy:${NC}"
65 | docker ps --filter "name=mcp-atlassian"
66 | echo ""
67 | echo -e "${GREEN}Hiển thị logs của container mcp-atlassian...${NC}"
68 | docker logs -f mcp-atlassian
69 | ;;
70 | 4)
71 | echo -e "${GREEN}Cấu hình Cline để kết nối với MCP Server:${NC}"
72 | echo '{
73 | "mcpServers": {
74 | "atlassian-docker": {
75 | "command": "docker",
76 | "args": ["exec", "-i", "mcp-atlassian", "node", "dist/index.js"],
77 | "env": {}
78 | }
79 | }
80 | }'
81 | ;;
82 | 5)
83 | echo -e "${GREEN}Thoát chương trình.${NC}"
84 | exit 0
85 | ;;
86 | *)
87 | echo -e "${RED}Lựa chọn không hợp lệ.${NC}"
88 | exit 1
89 | ;;
90 | esac
```
--------------------------------------------------------------------------------
/src/schemas/common.ts:
--------------------------------------------------------------------------------
```typescript
1 | /**
2 | * Common schema definitions and metadata utilities for MCP resources
3 | */
4 |
5 | /**
6 | * Standard metadata interface for all resources
7 | */
8 | export interface StandardMetadata {
9 | total: number; // Total number of records
10 | limit: number; // Maximum number of records returned
11 | offset: number; // Starting position
12 | hasMore: boolean; // Whether there are more records
13 | links?: { // Useful links
14 | self: string; // Link to this resource
15 | ui?: string; // Link to Atlassian UI
16 | next?: string; // Link to next page
17 | }
18 | }
19 |
20 | /**
21 | * Creates standard metadata object for resource responses
22 | */
23 | export function createStandardMetadata(
24 | total: number,
25 | limit: number,
26 | offset: number,
27 | baseUrl: string,
28 | uiUrl?: string
29 | ): StandardMetadata {
30 | const hasMore = offset + limit < total;
31 |
32 | // Base metadata
33 | const metadata: StandardMetadata = {
34 | total,
35 | limit,
36 | offset,
37 | hasMore,
38 | links: {
39 | self: baseUrl
40 | }
41 | };
42 |
43 | // Add UI link if provided
44 | if (uiUrl) {
45 | metadata.links!.ui = uiUrl;
46 | }
47 |
48 | // Add next page link if there are more results
49 | if (hasMore) {
50 | // Parse the current URL
51 | try {
52 | const url = new URL(baseUrl);
53 | url.searchParams.set('offset', String(offset + limit));
54 | url.searchParams.set('limit', String(limit));
55 | metadata.links!.next = url.toString();
56 | } catch (error) {
57 | // If URL parsing fails, construct a simple next link
58 | const separator = baseUrl.includes('?') ? '&' : '?';
59 | metadata.links!.next = `${baseUrl}${separator}offset=${offset + limit}&limit=${limit}`;
60 | }
61 | }
62 |
63 | return metadata;
64 | }
65 |
66 | /**
67 | * JSON Schema definition for standard metadata
68 | */
69 | export const standardMetadataSchema = {
70 | type: "object",
71 | properties: {
72 | total: { type: "number", description: "Total number of records" },
73 | limit: { type: "number", description: "Maximum number of records returned" },
74 | offset: { type: "number", description: "Starting position" },
75 | hasMore: { type: "boolean", description: "Whether there are more records" },
76 | links: {
77 | type: "object",
78 | properties: {
79 | self: { type: "string", description: "Link to this resource" },
80 | ui: { type: "string", description: "Link to Atlassian UI" },
81 | next: { type: "string", description: "Link to next page" }
82 | }
83 | }
84 | },
85 | required: ["total", "limit", "offset", "hasMore"]
86 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/create-issue.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
4 | import { createIssue } from '../../utils/jira-tool-api-v3.js';
5 | import { ApiError } from '../../utils/error-handler.js';
6 | import { Logger } from '../../utils/logger.js';
7 | import { Tools, Config } from '../../utils/mcp-helpers.js';
8 |
9 | // Initialize logger
10 | const logger = Logger.getLogger('JiraTools:createIssue');
11 |
12 | // Input parameter schema
13 | export const createIssueSchema = z.object({
14 | projectKey: z.string().describe('Project key (e.g., PROJ)'),
15 | summary: z.string().describe('Issue summary'),
16 | issueType: z.string().default('Task').describe('Issue type (e.g., Bug, Task, Story)'),
17 | description: z.string().optional().describe('Issue description'),
18 | priority: z.string().optional().describe('Priority (e.g., High, Medium, Low)'),
19 | assignee: z.string().optional().describe('Assignee username'),
20 | labels: z.array(z.string()).optional().describe('Labels for the issue')
21 | });
22 |
23 | type CreateIssueParams = z.infer<typeof createIssueSchema>;
24 |
25 | async function createIssueToolImpl(params: CreateIssueParams, context: any) {
26 | const config: AtlassianConfig = Config.getConfigFromContextOrEnv(context);
27 | logger.info(`Creating new issue in project: ${params.projectKey}`);
28 | const additionalFields: Record<string, any> = {};
29 | if (params.priority) {
30 | additionalFields.priority = { name: params.priority };
31 | }
32 | if (params.assignee) {
33 | additionalFields.assignee = { name: params.assignee };
34 | }
35 | if (params.labels && params.labels.length > 0) {
36 | additionalFields.labels = params.labels;
37 | }
38 | const newIssue = await createIssue(
39 | config,
40 | params.projectKey,
41 | params.summary,
42 | params.description,
43 | params.issueType,
44 | additionalFields
45 | );
46 | return {
47 | id: newIssue.id,
48 | key: newIssue.key,
49 | self: newIssue.self,
50 | success: true
51 | };
52 | }
53 |
54 | export const registerCreateIssueTool = (server: McpServer) => {
55 | server.tool(
56 | 'createIssue',
57 | 'Create a new issue in Jira',
58 | createIssueSchema.shape,
59 | async (params: CreateIssueParams, context: Record<string, any>) => {
60 | try {
61 | const result = await createIssueToolImpl(params, context);
62 | return {
63 | content: [
64 | {
65 | type: 'text',
66 | text: JSON.stringify(result)
67 | }
68 | ]
69 | };
70 | } catch (error) {
71 | return {
72 | content: [
73 | {
74 | type: 'text',
75 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
76 | }
77 | ],
78 | isError: true
79 | };
80 | }
81 | }
82 | );
83 | };
```
--------------------------------------------------------------------------------
/src/utils/logger.ts:
--------------------------------------------------------------------------------
```typescript
1 | import dotenv from 'dotenv';
2 |
3 | // Load environment variables
4 | dotenv.config();
5 |
6 | // Define log levels
7 | export enum LogLevel {
8 | ERROR = 0,
9 | WARN = 1,
10 | INFO = 2,
11 | DEBUG = 3
12 | }
13 |
14 | // Define colors for output
15 | const COLORS = {
16 | RESET: '\x1b[0m',
17 | RED: '\x1b[31m',
18 | YELLOW: '\x1b[33m',
19 | BLUE: '\x1b[34m',
20 | GRAY: '\x1b[90m'
21 | };
22 |
23 | // Get log level from environment variable
24 | const getLogLevelFromEnv = (): LogLevel => {
25 | const logLevel = process.env.LOG_LEVEL?.toLowerCase();
26 | switch (logLevel) {
27 | case 'debug':
28 | return LogLevel.DEBUG;
29 | case 'info':
30 | return LogLevel.INFO;
31 | case 'warn':
32 | return LogLevel.WARN;
33 | case 'error':
34 | return LogLevel.ERROR;
35 | default:
36 | return LogLevel.INFO; // Default is INFO
37 | }
38 | };
39 |
40 | /**
41 | * Logger utility
42 | */
43 | export class Logger {
44 | private static logLevel = getLogLevelFromEnv();
45 | private moduleName: string;
46 |
47 | /**
48 | * Initialize logger
49 | * @param moduleName Module name using the logger
50 | */
51 | constructor(moduleName: string) {
52 | this.moduleName = moduleName;
53 | }
54 |
55 | /**
56 | * Log error
57 | * @param message Log message
58 | * @param data Additional data (optional)
59 | */
60 | error(message: string, data?: any): void {
61 | if (Logger.logLevel >= LogLevel.ERROR) {
62 | console.error(`${COLORS.RED}[ERROR][${this.moduleName}]${COLORS.RESET} ${message}`);
63 | if (data) console.error(data);
64 | }
65 | }
66 |
67 | /**
68 | * Log warning
69 | * @param message Log message
70 | * @param data Additional data (optional)
71 | */
72 | warn(message: string, data?: any): void {
73 | if (Logger.logLevel >= LogLevel.WARN) {
74 | console.warn(`${COLORS.YELLOW}[WARN][${this.moduleName}]${COLORS.RESET} ${message}`);
75 | if (data) console.warn(data);
76 | }
77 | }
78 |
79 | /**
80 | * Log info
81 | * @param message Log message
82 | * @param data Additional data (optional)
83 | */
84 | info(message: string, data?: any): void {
85 | if (Logger.logLevel >= LogLevel.INFO) {
86 | console.info(`${COLORS.BLUE}[INFO][${this.moduleName}]${COLORS.RESET} ${message}`);
87 | if (data) console.info(data);
88 | }
89 | }
90 |
91 | /**
92 | * Log debug
93 | * @param message Log message
94 | * @param data Additional data (optional)
95 | */
96 | debug(message: string, data?: any): void {
97 | if (Logger.logLevel >= LogLevel.DEBUG) {
98 | console.debug(`${COLORS.GRAY}[DEBUG][${this.moduleName}]${COLORS.RESET} ${message}`);
99 | if (data) console.debug(data);
100 | }
101 | }
102 |
103 | /**
104 | * Create a logger instance
105 | * @param moduleName Module name using the logger
106 | * @returns Logger instance
107 | */
108 | static getLogger(moduleName: string): Logger {
109 | return new Logger(moduleName);
110 | }
111 |
112 | /**
113 | * Set log level
114 | * @param level New log level
115 | */
116 | static setLogLevel(level: LogLevel): void {
117 | Logger.logLevel = level;
118 | }
119 | }
```
--------------------------------------------------------------------------------
/src/tests/e2e/mcp-server.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { McpClient } from '@modelcontextprotocol/sdk/client/mcp.js';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { InMemoryClientServerPair } from '@modelcontextprotocol/sdk/server/memory.js';
4 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
5 | import { registerGetIssueTool } from '../../tools/jira/get-issue.js';
6 | import { registerSearchIssuesTool } from '../../tools/jira/search-issues.js';
7 | import { registerGetPageTool } from '../../tools/confluence/get-page.js';
8 | import { registerGetSpacesTool } from '../../tools/confluence/get-spaces.js';
9 | import dotenv from 'dotenv';
10 |
11 | // Tải biến môi trường
12 | dotenv.config();
13 |
14 | describe('MCP Server E2E Tests', () => {
15 | let server: McpServer;
16 | let client: McpClient;
17 | let testConfig: AtlassianConfig;
18 |
19 | beforeEach(() => {
20 | // Thiết lập cấu hình test từ biến môi trường
21 | const ATLASSIAN_SITE_NAME = process.env.ATLASSIAN_SITE_NAME || 'test-site';
22 | const ATLASSIAN_USER_EMAIL = process.env.ATLASSIAN_USER_EMAIL || '[email protected]';
23 | const ATLASSIAN_API_TOKEN = process.env.ATLASSIAN_API_TOKEN || 'test-token';
24 |
25 | testConfig = {
26 | baseUrl: `https://${ATLASSIAN_SITE_NAME}.atlassian.net`,
27 | email: ATLASSIAN_USER_EMAIL,
28 | apiToken: ATLASSIAN_API_TOKEN
29 | };
30 |
31 | // Tạo cặp server-client cho test
32 | const pair = new InMemoryClientServerPair();
33 | server = new McpServer({
34 | name: 'mcp-atlassian-test-server',
35 | version: '1.0.0'
36 | });
37 | client = new McpClient();
38 |
39 | // Kết nối server và client
40 | server.connect(pair.serverTransport);
41 | client.connect(pair.clientTransport);
42 |
43 | // Đăng ký context cho mỗi tool handler
44 | const context = new Map<string, any>();
45 | context.set('atlassianConfig', testConfig);
46 |
47 | // Đăng ký một số tools để test
48 | registerGetIssueTool(server);
49 | registerSearchIssuesTool(server);
50 | registerGetPageTool(server);
51 | registerGetSpacesTool(server);
52 | });
53 |
54 | afterEach(() => {
55 | // Đóng kết nối sau mỗi test
56 | server.close();
57 | client.close();
58 | });
59 |
60 | test('Server should register tools correctly', async () => {
61 | // Lấy danh sách tools đã đăng ký
62 | const tools = await client.getToolList();
63 |
64 | // Kiểm tra số lượng tools đã đăng ký
65 | expect(tools.length).toBeGreaterThan(0);
66 |
67 | // Kiểm tra các tools cụ thể
68 | const toolNames = tools.map(tool => tool.name);
69 | expect(toolNames).toContain('getIssue');
70 | expect(toolNames).toContain('searchIssues');
71 | expect(toolNames).toContain('getPage');
72 | expect(toolNames).toContain('getSpaces');
73 | });
74 |
75 | // Thêm các test case cho tool calls sẽ được bổ sung sau
76 | // khi hoàn thiện việc cập nhật API các tools
77 |
78 | test.todo('Should call getIssue tool successfully');
79 | test.todo('Should call searchIssues tool successfully');
80 | test.todo('Should handle error cases properly');
81 | });
```
--------------------------------------------------------------------------------
/src/tools/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { registerCreateIssueTool } from './jira/create-issue.js';
2 | import { registerUpdateIssueTool } from './jira/update-issue.js';
3 | import { registerTransitionIssueTool } from './jira/transition-issue.js';
4 | import { registerAssignIssueTool } from './jira/assign-issue.js';
5 | import { registerCreateFilterTool } from './jira/create-filter.js';
6 | import { registerUpdateFilterTool } from './jira/update-filter.js';
7 | import { registerDeleteFilterTool } from './jira/delete-filter.js';
8 | import { registerCreateSprintTool } from './jira/create-sprint.js';
9 | import { registerCreatePageTool } from './confluence/create-page.js';
10 | import { registerUpdatePageTool } from './confluence/update-page.js';
11 | import { registerAddCommentTool } from './confluence/add-comment.js';
12 | import { registerDeletePageTool } from './confluence/delete-page.js';
13 | import { registerUpdatePageTitleTool } from './confluence/update-page-title.js';
14 | import { registerUpdateFooterCommentTool } from './confluence/update-footer-comment.js';
15 | import { registerDeleteFooterCommentTool } from './confluence/delete-footer-comment.js';
16 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17 | import { registerStartSprintTool } from './jira/start-sprint.js';
18 | import { registerCloseSprintTool } from './jira/close-sprint.js';
19 | import { registerAddIssuesToBacklogTool } from './jira/add-issues-to-backlog.js';
20 | import { registerRankBacklogIssuesTool } from './jira/rank-backlog-issues.js';
21 | import { registerCreateDashboardTool } from './jira/create-dashboard.js';
22 | import { registerUpdateDashboardTool } from './jira/update-dashboard.js';
23 | import { registerAddGadgetToDashboardTool } from './jira/add-gadget-to-dashboard.js';
24 | import { registerRemoveGadgetFromDashboardTool } from './jira/remove-gadget-from-dashboard.js';
25 | import { registerAddIssueToSprintTool } from './jira/add-issue-to-sprint.js';
26 |
27 | /**
28 | * Register all tools with MCP Server
29 | * @param server MCP Server instance
30 | */
31 | export function registerAllTools(server: McpServer) {
32 | // Jira issue tools
33 | registerCreateIssueTool(server);
34 | registerUpdateIssueTool(server);
35 | registerTransitionIssueTool(server);
36 | registerAssignIssueTool(server);
37 |
38 | // Jira filter tools
39 | registerCreateFilterTool(server);
40 | registerUpdateFilterTool(server);
41 | registerDeleteFilterTool(server);
42 |
43 | // Jira sprint tools
44 | registerCreateSprintTool(server);
45 | registerStartSprintTool(server);
46 | registerCloseSprintTool(server);
47 |
48 | // Jira board tools
49 | // registerAddIssueToBoardTool(server);
50 |
51 | // Jira backlog tools
52 | registerAddIssuesToBacklogTool(server);
53 | registerRankBacklogIssuesTool(server);
54 |
55 | // Jira dashboard/gadget tools
56 | registerCreateDashboardTool(server);
57 | registerUpdateDashboardTool(server);
58 | registerAddGadgetToDashboardTool(server);
59 | registerRemoveGadgetFromDashboardTool(server);
60 |
61 | // Confluence tools
62 | registerCreatePageTool(server);
63 | registerUpdatePageTool(server);
64 | registerAddCommentTool(server);
65 | registerDeletePageTool(server);
66 | registerUpdatePageTitleTool(server);
67 | registerUpdateFooterCommentTool(server);
68 | registerDeleteFooterCommentTool(server);
69 |
70 | registerAddIssueToSprintTool(server);
71 | }
72 |
```
--------------------------------------------------------------------------------
/src/tools/jira/update-issue.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
3 | import { updateIssue } from '../../utils/jira-tool-api-v3.js';
4 | import { ApiError } from '../../utils/error-handler.js';
5 | import { Logger } from '../../utils/logger.js';
6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7 | import { Tools, Config } from '../../utils/mcp-helpers.js';
8 |
9 | // Initialize logger
10 | const logger = Logger.getLogger('JiraTools:updateIssue');
11 |
12 | // Input parameter schema
13 | export const updateIssueSchema = z.object({
14 | issueIdOrKey: z.string().describe('ID or key of the issue to update (e.g., PROJ-123)'),
15 | summary: z.string().optional().describe('New summary of the issue'),
16 | description: z.string().optional().describe('New description of the issue'),
17 | priority: z.string().optional().describe('New priority (e.g., High, Medium, Low)'),
18 | labels: z.array(z.string()).optional().describe('New labels for the issue'),
19 | customFields: z.record(z.any()).optional().describe('Custom fields to update')
20 | });
21 |
22 | type UpdateIssueParams = z.infer<typeof updateIssueSchema>;
23 |
24 | async function updateIssueToolImpl(params: UpdateIssueParams, context: any) {
25 | const config: AtlassianConfig = Config.getConfigFromContextOrEnv(context);
26 | logger.info(`Updating issue: ${params.issueIdOrKey}`);
27 | const fields: Record<string, any> = {};
28 | if (params.summary) {
29 | fields.summary = params.summary;
30 | }
31 | if (params.description) {
32 | fields.description = {
33 | version: 1,
34 | type: 'doc',
35 | content: [
36 | {
37 | type: 'paragraph',
38 | content: [
39 | {
40 | type: 'text',
41 | text: params.description
42 | }
43 | ]
44 | }
45 | ]
46 | };
47 | }
48 | if (params.priority) {
49 | fields.priority = { name: params.priority };
50 | }
51 | if (params.labels) {
52 | fields.labels = params.labels;
53 | }
54 | if (params.customFields) {
55 | Object.entries(params.customFields).forEach(([key, value]) => {
56 | fields[key] = value;
57 | });
58 | }
59 | if (Object.keys(fields).length === 0) {
60 | return {
61 | issueIdOrKey: params.issueIdOrKey,
62 | success: false,
63 | message: 'No fields provided to update'
64 | };
65 | }
66 | const result = await updateIssue(
67 | config,
68 | params.issueIdOrKey,
69 | fields
70 | );
71 | return {
72 | issueIdOrKey: params.issueIdOrKey,
73 | success: result.success,
74 | message: result.message
75 | };
76 | }
77 |
78 | export const registerUpdateIssueTool = (server: McpServer) => {
79 | server.tool(
80 | 'updateIssue',
81 | 'Update information of a Jira issue',
82 | updateIssueSchema.shape,
83 | async (params: UpdateIssueParams, context: Record<string, any>) => {
84 | try {
85 | const result = await updateIssueToolImpl(params, context);
86 | return {
87 | content: [
88 | {
89 | type: 'text',
90 | text: JSON.stringify(result)
91 | }
92 | ]
93 | };
94 | } catch (error) {
95 | return {
96 | content: [
97 | {
98 | type: 'text',
99 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
100 | }
101 | ],
102 | isError: true
103 | };
104 | }
105 | }
106 | );
107 | };
```
--------------------------------------------------------------------------------
/docs/knowledge/03-mcp-prompts-sampling.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Server: Prompting and Sampling Techniques
2 |
3 | This document provides guidance on effectively using MCP resources and tools with AI models, focusing on prompt design and sampling strategies. It covers best practices for integrating MCP with LLMs to maximize the utility of Atlassian data and operations.
4 |
5 | ## 1. Introduction to AI Integration with MCP
6 |
7 | *This section will be expanded in a future update.*
8 |
9 | ## 2. Prompt Engineering for MCP Resources
10 |
11 | *This section will be expanded in a future update.*
12 |
13 | ### 2.1. Resource Discovery Prompts
14 |
15 | *This section will be expanded in a future update.*
16 |
17 | ### 2.2. Effective Query Formulation
18 |
19 | *This section will be expanded in a future update.*
20 |
21 | ### 2.3. Parameter Selection Strategies
22 |
23 | *This section will be expanded in a future update.*
24 |
25 | ## 3. Tool Invocation Patterns
26 |
27 | *This section will be expanded in a future update.*
28 |
29 | ### 3.1. Identifying Tool Opportunities
30 |
31 | *This section will be expanded in a future update.*
32 |
33 | ### 3.2. Parameter Preparation
34 |
35 | *This section will be expanded in a future update.*
36 |
37 | ### 3.3. Multi-step Tool Sequences
38 |
39 | *This section will be expanded in a future update.*
40 |
41 | ## 4. Data Sampling Techniques
42 |
43 | *This section will be expanded in a future update.*
44 |
45 | ### 4.1. Representative Sampling
46 |
47 | *This section will be expanded in a future update.*
48 |
49 | ### 4.2. Handling Large Datasets
50 |
51 | *This section will be expanded in a future update.*
52 |
53 | ### 4.3. Context Window Optimization
54 |
55 | *This section will be expanded in a future update.*
56 |
57 | ## 5. Response Processing
58 |
59 | *This section will be expanded in a future update.*
60 |
61 | ### 5.1. Extracting Structured Data
62 |
63 | *This section will be expanded in a future update.*
64 |
65 | ### 5.2. ADF Content Handling
66 |
67 | *This section will be expanded in a future update.*
68 |
69 | ### 5.3. Error Interpretation
70 |
71 | *This section will be expanded in a future update.*
72 |
73 | ## 6. Advanced Integration Techniques
74 |
75 | *This section will be expanded in a future update.*
76 |
77 | ### 6.1. Multi-resource Correlation
78 |
79 | *This section will be expanded in a future update.*
80 |
81 | ### 6.2. Workflow Automation
82 |
83 | *This section will be expanded in a future update.*
84 |
85 | ### 6.3. Decision Support Systems
86 |
87 | *This section will be expanded in a future update.*
88 |
89 | ## 7. Performance Optimization
90 |
91 | *This section will be expanded in a future update.*
92 |
93 | ### 7.1. Reducing Token Usage
94 |
95 | *This section will be expanded in a future update.*
96 |
97 | ### 7.2. Caching Strategies
98 |
99 | *This section will be expanded in a future update.*
100 |
101 | ### 7.3. Request Batching
102 |
103 | *This section will be expanded in a future update.*
104 |
105 | ## 8. Case Studies
106 |
107 | *This section will be expanded in a future update.*
108 |
109 | ### 8.1. Project Management Assistant
110 |
111 | *This section will be expanded in a future update.*
112 |
113 | ### 8.2. Documentation Generator
114 |
115 | *This section will be expanded in a future update.*
116 |
117 | ### 8.3. Issue Analyzer
118 |
119 | *This section will be expanded in a future update.*
120 |
121 | ## 9. References
122 |
123 | - [MCP Protocol Documentation](https://github.com/modelcontextprotocol/mcp)
124 | - [Prompt Engineering Guide](https://www.promptingguide.ai/)
125 | - [MCP Server Architecture Overview](01-mcp-overview-architecture.md)
126 | - [MCP Tools and Resources Guide](02-mcp-tools-resources.md)
127 |
128 | ---
129 |
130 | *This document is a placeholder and will be expanded in a future update.*
```
--------------------------------------------------------------------------------
/RELEASE_NOTES.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Atlassian Server 2.1.1
2 |
3 | 🚀 **Major refactor: Standardized resource/tool structure, removed content-metadata resource, updated developer documentation!**
4 |
5 | Available on npm (@phuc-nt/mcp-atlassian-server) or download directly. Use with Cline or any MCP-compatible client.
6 |
7 | ---
8 |
9 | ### Updates in 2.1.1
10 |
11 | **Refactor & Standardization**
12 | - Refactored the entire codebase to standardize resource/tool structure, completely removed the content-metadata resource, and merged metadata into the page resource.
13 | - Updated and standardized developer documentation, making it easy for any developer to extend and maintain.
14 | - Ensured compatibility with the latest MCP SDK, improved security, scalability, and maintainability.
15 | - Updated `docs/introduction/resources-and-tools.md` to remove all references to content-metadata.
16 |
17 | **Bug Fixes**
18 | - Fixed duplicate resource registration issues for a more stable experience
19 | - Improved resource management and registration process
20 | - Resolved issues with conflicting resource patterns
21 |
22 | **Documentation Series**
23 | - Added comprehensive documentation series:
24 | 1. MCP Overview & Architecture: Core concepts and design principles
25 | 2. MCP Tools & Resources Development: How to develop and extend resources/tools
26 | 3. MCP Prompts & Sampling: Guide for prompt engineering with MCP
27 | - Updated installation guide and client development documentation
28 | - Enhanced resource and tool descriptions
29 |
30 | **Core Features**
31 | **Jira Information Access**
32 | - View issues, projects, users, comments, transitions, assignable users
33 | - Access boards, sprints, filters, dashboards and gadgets
34 | - Search issues with powerful filter tools
35 |
36 | **Jira Actions**
37 | - Create, update, transition, assign issues
38 | - Manage boards and sprints for Agile/Scrum workflows
39 | - Create/update dashboards, add/remove gadgets
40 | - Create, update, and delete filters
41 |
42 | **Confluence Information Access**
43 | - View spaces, pages, child pages, details, comments, labels
44 | - Access page versions and attachments
45 | - View and search comments
46 |
47 | **Confluence Actions**
48 | - Create and update pages, add/remove labels, add comments
49 | - Manage page versions, upload/download attachments
50 | - Update and delete comments
51 | - Delete pages
52 |
53 | ---
54 |
55 | **How to use:**
56 | 1. Install from npm: `npm install -g @phuc-nt/mcp-atlassian-server`
57 | 2. Point Cline config to the installed package.
58 | 3. Set your Atlassian API credentials.
59 | 4. Start using natural language to work with Jira & Confluence!
60 |
61 | See [README.md](https://github.com/phuc-nt/mcp-atlassian-server) and the new documentation series for full instructions.
62 | Feedback and contributions are welcome! 🚀
63 |
64 | ## What's Changed
65 | * Fixed resource registration to prevent duplicates
66 | * Improved server stability and resource management
67 | * Added comprehensive documentation series in `docs/knowledge/`
68 | * Enhanced development guide for client integrations
69 | * Updated resource structure for better organization
70 |
71 | **Previous Changelog (2.0.0)**:
72 | * Updated to latest Atlassian APIs (Jira API v3, Confluence API v2)
73 | * Redesigned resource and tool structure for better organization
74 | * Expanded Jira capabilities with board, sprint, dashboard, and filter management
75 | * Enhanced Confluence features with advanced page operations and comment management
76 |
77 | **Full Changelog**: https://github.com/phuc-nt/mcp-atlassian-server/blob/main/CHANGELOG.md
```
--------------------------------------------------------------------------------
/src/tools/confluence/delete-footer-comment.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
3 | import { deleteConfluenceFooterCommentV2 } from '../../utils/confluence-tool-api.js';
4 | import { ApiError, ApiErrorType } from '../../utils/error-handler.js';
5 | import { Logger } from '../../utils/logger.js';
6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7 | import { Config } from '../../utils/mcp-helpers.js';
8 |
9 | const logger = Logger.getLogger('ConfluenceTools:deleteFooterComment');
10 |
11 | export const deleteFooterCommentSchema = z.object({
12 | commentId: z.union([z.string(), z.number()]).describe('ID of the comment to delete (required)')
13 | });
14 |
15 | type DeleteFooterCommentParams = z.infer<typeof deleteFooterCommentSchema>;
16 |
17 | export async function deleteFooterCommentHandler(
18 | params: DeleteFooterCommentParams,
19 | config: AtlassianConfig
20 | ): Promise<{ success: boolean; id: string|number; message: string }> {
21 | try {
22 | logger.info(`Deleting footer comment (v2) with ID: ${params.commentId}`);
23 | await deleteConfluenceFooterCommentV2(config, params.commentId);
24 | return {
25 | success: true,
26 | id: params.commentId,
27 | message: `Footer comment ${params.commentId} đã được xóa vĩnh viễn.`
28 | };
29 | } catch (error) {
30 | if (error instanceof ApiError) throw error;
31 | logger.error(`Error deleting footer comment (v2) with ID ${params.commentId}:`, error);
32 | throw new ApiError(ApiErrorType.SERVER_ERROR, `Failed to delete footer comment: ${error instanceof Error ? error.message : String(error)}`, 500);
33 | }
34 | }
35 |
36 | export const registerDeleteFooterCommentTool = (server: McpServer) => {
37 | server.tool(
38 | 'deleteFooterComment',
39 | 'Delete a footer comment in Confluence (API v2)',
40 | deleteFooterCommentSchema.shape,
41 | async (params: DeleteFooterCommentParams, context: Record<string, any>) => {
42 | try {
43 | const config = context?.atlassianConfig ?? Config.getAtlassianConfigFromEnv();
44 | if (!config) {
45 | return {
46 | content: [
47 | { type: 'text', text: 'Invalid or missing Atlassian configuration' }
48 | ],
49 | isError: true
50 | };
51 | }
52 | const result = await deleteFooterCommentHandler(params, config);
53 | return {
54 | content: [
55 | {
56 | type: 'text',
57 | text: JSON.stringify({
58 | success: true,
59 | message: result.message,
60 | id: result.id
61 | })
62 | }
63 | ]
64 | };
65 | } catch (error) {
66 | if (error instanceof ApiError) {
67 | return {
68 | content: [
69 | {
70 | type: 'text',
71 | text: JSON.stringify({
72 | success: false,
73 | message: error.message,
74 | code: error.code,
75 | statusCode: error.statusCode,
76 | type: error.type
77 | })
78 | }
79 | ],
80 | isError: true
81 | };
82 | }
83 | return {
84 | content: [
85 | {
86 | type: 'text',
87 | text: JSON.stringify({
88 | success: false,
89 | message: `Error while deleting footer comment: ${error instanceof Error ? error.message : String(error)}`
90 | })
91 | }
92 | ],
93 | isError: true
94 | };
95 | }
96 | }
97 | );
98 | };
```
--------------------------------------------------------------------------------
/src/tools/confluence/delete-page.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
3 | import { ApiError, ApiErrorType } from '../../utils/error-handler.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6 | import { McpResponse, createSuccessResponse, createErrorResponse } from '../../utils/mcp-core.js';
7 | import { deleteConfluencePageV2 } from '../../utils/confluence-tool-api.js';
8 | import { Config } from '../../utils/mcp-helpers.js';
9 |
10 | const logger = Logger.getLogger('ConfluenceTools:deletePage');
11 |
12 | export const deletePageSchema = z.object({
13 | pageId: z.string().describe('ID of the page to delete (required)'),
14 | draft: z.boolean().optional().describe('Delete draft version if true'),
15 | purge: z.boolean().optional().describe('Permanently delete (purge) if true')
16 | });
17 |
18 | type DeletePageParams = z.infer<typeof deletePageSchema>;
19 |
20 | // Main handler
21 | export async function deletePageHandler(
22 | params: DeletePageParams,
23 | config: AtlassianConfig
24 | ): Promise<{ success: boolean; message: string }> {
25 | try {
26 | logger.info(`Deleting page (v2) with ID: ${params.pageId}`);
27 | await deleteConfluencePageV2(config, params);
28 | return { success: true, message: `Page ${params.pageId} deleted successfully.` };
29 | } catch (error) {
30 | if (error instanceof ApiError) {
31 | throw error;
32 | }
33 | logger.error(`Error deleting page (v2) with ID ${params.pageId}:`, error);
34 | let message = `Failed to delete page: ${error instanceof Error ? error.message : String(error)}`;
35 | throw new ApiError(
36 | ApiErrorType.SERVER_ERROR,
37 | message,
38 | 500
39 | );
40 | }
41 | }
42 |
43 | // Register tool
44 | export const registerDeletePageTool = (server: McpServer) => {
45 | server.tool(
46 | 'deletePage',
47 | 'Delete a Confluence page (API v2)',
48 | deletePageSchema.shape,
49 | async (params: DeletePageParams, context: Record<string, any>) => {
50 | try {
51 | const config = context?.atlassianConfig ?? Config.getAtlassianConfigFromEnv();
52 | if (!config) {
53 | return {
54 | content: [
55 | { type: 'text', text: 'Invalid or missing Atlassian configuration' }
56 | ],
57 | isError: true
58 | };
59 | }
60 | const result = await deletePageHandler(params, config);
61 | return {
62 | content: [
63 | {
64 | type: 'text',
65 | text: JSON.stringify({
66 | success: true,
67 | message: result.message
68 | })
69 | }
70 | ]
71 | };
72 | } catch (error) {
73 | if (error instanceof ApiError) {
74 | return {
75 | content: [
76 | {
77 | type: 'text',
78 | text: JSON.stringify({
79 | success: false,
80 | message: error.message,
81 | code: error.code,
82 | statusCode: error.statusCode,
83 | type: error.type
84 | })
85 | }
86 | ],
87 | isError: true
88 | };
89 | }
90 | return {
91 | content: [
92 | {
93 | type: 'text',
94 | text: JSON.stringify({
95 | success: false,
96 | message: `Error while deleting page: ${error instanceof Error ? error.message : String(error)}`
97 | })
98 | }
99 | ],
100 | isError: true
101 | };
102 | }
103 | }
104 | );
105 | };
```
--------------------------------------------------------------------------------
/src/tools/jira/add-gadget-to-dashboard.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3 | import { addGadgetToDashboard } from '../../utils/jira-tool-api-v3.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { Tools, Config } from '../../utils/mcp-helpers.js';
6 |
7 | const logger = Logger.getLogger('JiraTools:addGadgetToDashboard');
8 |
9 | const colorEnum = z.enum(['blue', 'red', 'yellow', 'green', 'cyan', 'purple', 'gray', 'white']);
10 |
11 | const addGadgetToDashboardBaseSchema = z.object({
12 | dashboardId: z.string().describe('Dashboard ID'),
13 | moduleKey: z.string().optional().describe('Gadget moduleKey (recommended, e.g. "com.atlassian.plugins.atlassian-connect-plugin:sample-dashboard-item"). Only one of moduleKey or uri should be provided.'),
14 | uri: z.string().optional().describe('Gadget URI (legacy, e.g. "/rest/gadgets/1.0/g/com.atlassian.jira.gadgets:filter-results-gadget/gadgets/filter-results-gadget.xml"). Only one of moduleKey or uri should be provided.'),
15 | title: z.string().optional().describe('Gadget title (optional)'),
16 | color: colorEnum.describe('Gadget color. Must be one of: blue, red, yellow, green, cyan, purple, gray, white.'),
17 | position: z.object({
18 | column: z.number().describe('Column index (0-based)'),
19 | row: z.number().describe('Row index (0-based)')
20 | }).optional().describe('Position of the gadget on the dashboard (optional)')
21 | });
22 |
23 | export const addGadgetToDashboardSchema = addGadgetToDashboardBaseSchema.refine(
24 | (data) => !!data.moduleKey !== !!data.uri,
25 | { message: 'You must provide either moduleKey or uri, but not both.' }
26 | );
27 |
28 | type AddGadgetToDashboardParams = z.infer<typeof addGadgetToDashboardBaseSchema>;
29 |
30 | async function addGadgetToDashboardToolImpl(params: AddGadgetToDashboardParams, context: any) {
31 | if (!!params.moduleKey === !!params.uri) {
32 | return {
33 | success: false,
34 | error: 'You must provide either moduleKey or uri, but not both.'
35 | };
36 | }
37 | const config = Config.getConfigFromContextOrEnv(context);
38 | const { dashboardId, moduleKey, uri, ...rest } = params;
39 | let gadgetUri = uri;
40 | if (!gadgetUri && moduleKey) {
41 | return {
42 | success: false,
43 | error: 'Jira Cloud API chỉ hỗ trợ thêm gadget qua uri. Vui lòng cung cấp uri hợp lệ.'
44 | };
45 | }
46 | if (!gadgetUri) {
47 | return {
48 | success: false,
49 | error: 'Thiếu uri gadget.'
50 | };
51 | }
52 | const data = { uri: gadgetUri, ...rest };
53 | const result = await addGadgetToDashboard(config, dashboardId, data);
54 | return {
55 | success: true,
56 | dashboardId,
57 | uri: gadgetUri,
58 | ...rest,
59 | result
60 | };
61 | }
62 |
63 | export const registerAddGadgetToDashboardTool = (server: McpServer) => {
64 | server.tool(
65 | 'addGadgetToDashboard',
66 | 'Add gadget to Jira dashboard (POST /rest/api/3/dashboard/{dashboardId}/gadget)',
67 | addGadgetToDashboardBaseSchema.shape,
68 | async (params: AddGadgetToDashboardParams, context: Record<string, any>) => {
69 | try {
70 | const result = await addGadgetToDashboardToolImpl(params, context);
71 | return {
72 | content: [
73 | {
74 | type: 'text',
75 | text: JSON.stringify(result)
76 | }
77 | ]
78 | };
79 | } catch (error) {
80 | logger.error('Error in addGadgetToDashboard:', error);
81 | return {
82 | content: [
83 | {
84 | type: 'text',
85 | text: JSON.stringify({ success: false, error: error instanceof Error ? error.message : String(error) })
86 | }
87 | ],
88 | isError: true
89 | };
90 | }
91 | }
92 | );
93 | };
```
--------------------------------------------------------------------------------
/dev_mcp-atlassian-test-client/src/test-jira-projects.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3 | import path from 'path';
4 | import { fileURLToPath } from "url";
5 | import fs from "fs";
6 |
7 | // Get current file path
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 |
11 | // Load environment variables from .env
12 | function loadEnv(): Record<string, string> {
13 | try {
14 | const envFile = path.resolve(process.cwd(), '.env');
15 | const envContent = fs.readFileSync(envFile, 'utf8');
16 | const envVars: Record<string, string> = {};
17 | envContent.split('\n').forEach(line => {
18 | if (line.trim().startsWith('#') || !line.trim()) return;
19 | const [key, ...valueParts] = line.split('=');
20 | if (key && valueParts.length > 0) {
21 | const value = valueParts.join('=');
22 | envVars[key.trim()] = value.trim();
23 | }
24 | });
25 | return envVars;
26 | } catch (error) {
27 | console.error("Error loading .env file:", error);
28 | return {};
29 | }
30 | }
31 |
32 | // Print metadata and schema
33 | function printResourceMetaAndSchema(res: any) {
34 | if (res.contents && res.contents.length > 0) {
35 | const content = res.contents[0];
36 | // Print metadata if exists
37 | if (content.metadata) {
38 | console.log("Metadata:", content.metadata);
39 | }
40 | // Print schema if exists
41 | if (content.schema) {
42 | console.log("Schema:", JSON.stringify(content.schema, null, 2));
43 | }
44 | // Try to parse text if exists
45 | if (content.text) {
46 | try {
47 | const data = JSON.parse(String(content.text));
48 | if (Array.isArray(data)) {
49 | console.log("Data (array, first element):", data[0]);
50 | } else if (typeof data === 'object') {
51 | console.log("Data (object):", data);
52 | } else {
53 | console.log("Data:", data);
54 | }
55 | } catch {
56 | console.log("Cannot parse text.");
57 | }
58 | }
59 | }
60 | }
61 |
62 | async function main() {
63 | const client = new Client({
64 | name: "mcp-atlassian-test-client-jira-projects",
65 | version: "1.0.0"
66 | });
67 |
68 | // Path to MCP server
69 | const serverPath = "/Users/phucnt/Workspace/mcp-atlassian-server/dist/index.js";
70 |
71 | // Load environment variables
72 | const envVars = loadEnv();
73 | const processEnv: Record<string, string> = {};
74 | Object.keys(process.env).forEach(key => {
75 | if (process.env[key] !== undefined) {
76 | processEnv[key] = process.env[key] as string;
77 | }
78 | });
79 |
80 | // Initialize transport
81 | const transport = new StdioClientTransport({
82 | command: "node",
83 | args: [serverPath],
84 | env: {
85 | ...processEnv,
86 | ...envVars
87 | }
88 | });
89 |
90 | // Connect to server
91 | console.log("Connecting to MCP server...");
92 | await client.connect(transport);
93 |
94 | console.log("\n=== Test Jira Projects Resource ===");
95 |
96 | // Change projectKey to match your environment if needed
97 | const projectKey = "XDEMO2";
98 |
99 | const resourceUris = [
100 | `jira://projects`,
101 | `jira://projects/${projectKey}`,
102 | `jira://projects/${projectKey}/roles`
103 | ];
104 |
105 | for (const uri of resourceUris) {
106 | try {
107 | console.log(`\nResource: ${uri}`);
108 | const res = await client.readResource({ uri });
109 | if (uri === "jira://projects") {
110 | const projectsData = JSON.parse(String(res.contents[0].text));
111 | console.log("Number of projects:", projectsData.projects?.length || 0);
112 | }
113 | printResourceMetaAndSchema(res);
114 | } catch (e) {
115 | console.error(`Resource ${uri} error:`, e instanceof Error ? e.message : e);
116 | }
117 | }
118 |
119 | console.log("\n=== Finished testing Jira Projects Resource! ===");
120 | await client.close();
121 | }
122 |
123 | main();
```
--------------------------------------------------------------------------------
/dev_mcp-atlassian-test-client/src/test-jira-issues.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3 | import path from 'path';
4 | import { fileURLToPath } from "url";
5 | import fs from "fs";
6 |
7 | // Get current file path
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 |
11 | // Load environment variables from .env
12 | function loadEnv(): Record<string, string> {
13 | try {
14 | const envFile = path.resolve(process.cwd(), '.env');
15 | const envContent = fs.readFileSync(envFile, 'utf8');
16 | const envVars: Record<string, string> = {};
17 | envContent.split('\n').forEach(line => {
18 | if (line.trim().startsWith('#') || !line.trim()) return;
19 | const [key, ...valueParts] = line.split('=');
20 | if (key && valueParts.length > 0) {
21 | const value = valueParts.join('=');
22 | envVars[key.trim()] = value.trim();
23 | }
24 | });
25 | return envVars;
26 | } catch (error) {
27 | console.error("Error loading .env file:", error);
28 | return {};
29 | }
30 | }
31 |
32 | // Print metadata and schema
33 | function printResourceMetaAndSchema(res: any) {
34 | if (res.contents && res.contents.length > 0) {
35 | const content = res.contents[0];
36 | // Print metadata if exists
37 | if (content.metadata) {
38 | console.log("Metadata:", content.metadata);
39 | }
40 | // Print schema if exists
41 | if (content.schema) {
42 | console.log("Schema:", JSON.stringify(content.schema, null, 2));
43 | }
44 | // Try to parse text if exists
45 | if (content.text) {
46 | try {
47 | const data = JSON.parse(String(content.text));
48 | if (Array.isArray(data)) {
49 | console.log("Data (array, first element):", data[0]);
50 | } else if (typeof data === 'object') {
51 | console.log("Data (object):", data);
52 | } else {
53 | console.log("Data:", data);
54 | }
55 | } catch {
56 | console.log("Cannot parse text.");
57 | }
58 | }
59 | }
60 | }
61 |
62 | async function main() {
63 | const client = new Client({
64 | name: "mcp-atlassian-test-client-jira-issues",
65 | version: "1.0.0"
66 | });
67 |
68 | // Path to MCP server
69 | const serverPath = "/Users/phucnt/Workspace/mcp-atlassian-server/dist/index.js";
70 |
71 | // Load environment variables
72 | const envVars = loadEnv();
73 | const processEnv: Record<string, string> = {};
74 | Object.keys(process.env).forEach(key => {
75 | if (process.env[key] !== undefined) {
76 | processEnv[key] = process.env[key] as string;
77 | }
78 | });
79 |
80 | // Initialize transport
81 | const transport = new StdioClientTransport({
82 | command: "node",
83 | args: [serverPath],
84 | env: {
85 | ...processEnv,
86 | ...envVars
87 | }
88 | });
89 |
90 | // Connect to server
91 | console.log("Connecting to MCP server...");
92 | await client.connect(transport);
93 |
94 | console.log("\n=== Test Jira Issues Resource ===");
95 |
96 | // Change issueKey to match your environment if needed
97 | const issueKey = "XDEMO2-53";
98 |
99 | const resourceUris = [
100 | `jira://issues`,
101 | `jira://issues/${issueKey}`,
102 | `jira://issues/${issueKey}/transitions`,
103 | `jira://issues/${issueKey}/comments`
104 | ];
105 |
106 | for (const uri of resourceUris) {
107 | try {
108 | console.log(`\nResource: ${uri}`);
109 | const res = await client.readResource({ uri });
110 | if (uri === "jira://issues") {
111 | const issuesData = JSON.parse(String(res.contents[0].text));
112 | console.log("Number of issues:", issuesData.issues?.length || 0);
113 | }
114 | printResourceMetaAndSchema(res);
115 | } catch (e) {
116 | console.error(`Resource ${uri} error:`, e instanceof Error ? e.message : e);
117 | }
118 | }
119 |
120 | console.log("\n=== Finished testing Jira Issues Resource! ===");
121 | await client.close();
122 | }
123 |
124 | main();
```
--------------------------------------------------------------------------------
/src/tools/confluence/update-page-title.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
3 | import { ApiError, ApiErrorType } from '../../utils/error-handler.js';
4 | import { Logger } from '../../utils/logger.js';
5 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
6 | import { McpResponse, createSuccessResponse, createErrorResponse } from '../../utils/mcp-core.js';
7 | import { updateConfluencePageTitleV2 } from '../../utils/confluence-tool-api.js';
8 | import { Config } from '../../utils/mcp-helpers.js';
9 |
10 | const logger = Logger.getLogger('ConfluenceTools:updatePageTitle');
11 |
12 | export const updatePageTitleSchema = z.object({
13 | pageId: z.string().describe('ID of the page to update the title (required)'),
14 | title: z.string().describe('New title of the page (required)'),
15 | version: z.number().describe('New version number (required, must be exactly one greater than the current version)')
16 | });
17 |
18 | type UpdatePageTitleParams = z.infer<typeof updatePageTitleSchema>;
19 |
20 | export async function updatePageTitleHandler(
21 | params: UpdatePageTitleParams,
22 | config: AtlassianConfig
23 | ): Promise<{ success: boolean; id: string; title: string; version: number; message: string }> {
24 | try {
25 | logger.info(`Updating page title (v2) with ID: ${params.pageId}`);
26 | const data = await updateConfluencePageTitleV2(config, params);
27 | return {
28 | success: true,
29 | id: data.id,
30 | title: data.title,
31 | version: data.version?.number,
32 | message: `Page ${data.id} title updated to "${data.title}" (version ${data.version?.number}) successfully.`
33 | };
34 | } catch (error) {
35 | if (error instanceof ApiError) {
36 | throw error;
37 | }
38 | logger.error(`Error updating page title (v2) with ID ${params.pageId}:`, error);
39 | let message = `Failed to update page title: ${error instanceof Error ? error.message : String(error)}`;
40 | throw new ApiError(
41 | ApiErrorType.SERVER_ERROR,
42 | message,
43 | 500
44 | );
45 | }
46 | }
47 |
48 | export const registerUpdatePageTitleTool = (server: McpServer) => {
49 | server.tool(
50 | 'updatePageTitle',
51 | 'Update the title of a Confluence page (API v2)',
52 | updatePageTitleSchema.shape,
53 | async (params: UpdatePageTitleParams, context: Record<string, any>) => {
54 | try {
55 | const config = context?.atlassianConfig ?? Config.getAtlassianConfigFromEnv();
56 | if (!config) {
57 | return {
58 | content: [
59 | { type: 'text', text: 'Invalid or missing Atlassian configuration' }
60 | ],
61 | isError: true
62 | };
63 | }
64 | const result = await updatePageTitleHandler(params, config);
65 | return {
66 | content: [
67 | {
68 | type: 'text',
69 | text: JSON.stringify({
70 | success: true,
71 | message: result.message,
72 | id: result.id,
73 | title: result.title,
74 | version: result.version
75 | })
76 | }
77 | ]
78 | };
79 | } catch (error) {
80 | if (error instanceof ApiError) {
81 | return {
82 | content: [
83 | {
84 | type: 'text',
85 | text: JSON.stringify({
86 | success: false,
87 | message: error.message,
88 | code: error.code,
89 | statusCode: error.statusCode,
90 | type: error.type
91 | })
92 | }
93 | ],
94 | isError: true
95 | };
96 | }
97 | return {
98 | content: [
99 | {
100 | type: 'text',
101 | text: JSON.stringify({
102 | success: false,
103 | message: `Error while updating page title: ${error instanceof Error ? error.message : String(error)}`
104 | })
105 | }
106 | ],
107 | isError: true
108 | };
109 | }
110 | }
111 | );
112 | };
```
--------------------------------------------------------------------------------
/src/tools/confluence/update-footer-comment.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
3 | import { updateConfluenceFooterCommentV2 } from '../../utils/confluence-tool-api.js';
4 | import { ApiError, ApiErrorType } from '../../utils/error-handler.js';
5 | import { Logger } from '../../utils/logger.js';
6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7 | import { Config } from '../../utils/mcp-helpers.js';
8 |
9 | const logger = Logger.getLogger('ConfluenceTools:updateFooterComment');
10 |
11 | export const updateFooterCommentSchema = z.object({
12 | commentId: z.union([z.string(), z.number()]).describe('ID of the comment to update (required)'),
13 | version: z.number().describe('New version number (required, must be exactly one greater than the current version)'),
14 | value: z.string().describe('New content of the comment (required)'),
15 | representation: z.string().optional().describe('Content representation, default is "storage"'),
16 | message: z.string().optional().describe('Update message (optional)')
17 | });
18 |
19 | type UpdateFooterCommentParams = z.infer<typeof updateFooterCommentSchema>;
20 |
21 | export async function updateFooterCommentHandler(
22 | params: UpdateFooterCommentParams,
23 | config: AtlassianConfig
24 | ): Promise<{ success: boolean; id: string|number; version: number; message: string }> {
25 | try {
26 | logger.info(`Updating footer comment (v2) with ID: ${params.commentId}`);
27 | const data = await updateConfluenceFooterCommentV2(config, params);
28 | return {
29 | success: true,
30 | id: params.commentId,
31 | version: data.version?.number,
32 | message: `Footer comment ${params.commentId} updated to version ${data.version?.number} thành công.`
33 | };
34 | } catch (error) {
35 | if (error instanceof ApiError) throw error;
36 | logger.error(`Error updating footer comment (v2) with ID ${params.commentId}:`, error);
37 | throw new ApiError(ApiErrorType.SERVER_ERROR, `Failed to update footer comment: ${error instanceof Error ? error.message : String(error)}`, 500);
38 | }
39 | }
40 |
41 | export const registerUpdateFooterCommentTool = (server: McpServer) => {
42 | server.tool(
43 | 'updateFooterComment',
44 | 'Update a footer comment in Confluence (API v2)',
45 | updateFooterCommentSchema.shape,
46 | async (params: UpdateFooterCommentParams, context: Record<string, any>) => {
47 | try {
48 | const config = context?.atlassianConfig ?? Config.getAtlassianConfigFromEnv();
49 | if (!config) {
50 | return {
51 | content: [
52 | { type: 'text', text: 'Invalid or missing Atlassian configuration' }
53 | ],
54 | isError: true
55 | };
56 | }
57 | const result = await updateFooterCommentHandler(params, config);
58 | return {
59 | content: [
60 | {
61 | type: 'text',
62 | text: JSON.stringify({
63 | success: true,
64 | message: result.message,
65 | id: result.id,
66 | version: result.version
67 | })
68 | }
69 | ]
70 | };
71 | } catch (error) {
72 | if (error instanceof ApiError) {
73 | return {
74 | content: [
75 | {
76 | type: 'text',
77 | text: JSON.stringify({
78 | success: false,
79 | message: error.message,
80 | code: error.code,
81 | statusCode: error.statusCode,
82 | type: error.type
83 | })
84 | }
85 | ],
86 | isError: true
87 | };
88 | }
89 | return {
90 | content: [
91 | {
92 | type: 'text',
93 | text: JSON.stringify({
94 | success: false,
95 | message: `Error while updating footer comment: ${error instanceof Error ? error.message : String(error)}`
96 | })
97 | }
98 | ],
99 | isError: true
100 | };
101 | }
102 | }
103 | );
104 | };
```
--------------------------------------------------------------------------------
/dev_mcp-atlassian-test-client/src/test-confluence-spaces.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3 | import path from 'path';
4 | import { fileURLToPath } from "url";
5 | import fs from "fs";
6 |
7 | // Get current file path
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 |
11 | // Load environment variables from .env
12 | function loadEnv(): Record<string, string> {
13 | try {
14 | const envFile = path.resolve(process.cwd(), '.env');
15 | const envContent = fs.readFileSync(envFile, 'utf8');
16 | const envVars: Record<string, string> = {};
17 | envContent.split('\n').forEach(line => {
18 | if (line.trim().startsWith('#') || !line.trim()) return;
19 | const [key, ...valueParts] = line.split('=');
20 | if (key && valueParts.length > 0) {
21 | const value = valueParts.join('=');
22 | envVars[key.trim()] = value.trim();
23 | }
24 | });
25 | return envVars;
26 | } catch (error) {
27 | console.error("Error loading .env file:", error);
28 | return {};
29 | }
30 | }
31 |
32 | // Print metadata and schema
33 | function printResourceMetaAndSchema(res: any) {
34 | if (res.contents && res.contents.length > 0) {
35 | const content = res.contents[0];
36 | // Print metadata if exists
37 | if (content.metadata) {
38 | console.log("Metadata:", content.metadata);
39 | }
40 | // Print schema if exists
41 | if (content.schema) {
42 | console.log("Schema:", JSON.stringify(content.schema, null, 2));
43 | }
44 | // Try to parse text if exists
45 | if (content.text) {
46 | try {
47 | const data = JSON.parse(String(content.text));
48 | if (Array.isArray(data)) {
49 | console.log("Data (array, first element):", data[0]);
50 | } else if (typeof data === 'object') {
51 | console.log("Data (object):", data);
52 | } else {
53 | console.log("Data:", data);
54 | }
55 | } catch {
56 | console.log("Cannot parse text.");
57 | }
58 | }
59 | }
60 | }
61 |
62 | async function main() {
63 | const client = new Client({
64 | name: "mcp-atlassian-test-client-confluence-spaces",
65 | version: "1.0.0"
66 | });
67 |
68 | // Path to MCP server
69 | const serverPath = "/Users/phucnt/Workspace/mcp-atlassian-server/dist/index.js";
70 |
71 | // Load environment variables
72 | const envVars = loadEnv();
73 | const processEnv: Record<string, string> = {};
74 | Object.keys(process.env).forEach(key => {
75 | if (process.env[key] !== undefined) {
76 | processEnv[key] = process.env[key] as string;
77 | }
78 | });
79 |
80 | // Initialize transport
81 | const transport = new StdioClientTransport({
82 | command: "node",
83 | args: [serverPath],
84 | env: {
85 | ...processEnv,
86 | ...envVars
87 | }
88 | });
89 |
90 | // Connect to server
91 | console.log("Connecting to MCP server...");
92 | await client.connect(transport);
93 |
94 | console.log("\n=== Test Confluence Spaces Resource ===");
95 |
96 | // Nếu có biến spaceKey hoặc pageId, hãy cập nhật:
97 | const spaceKey = "AWA1"; // Space key mới
98 | const homePageId = "19464453"; // Home page id mới
99 |
100 | const resourceUris = [
101 | `confluence://spaces`,
102 | `confluence://spaces/${spaceKey}`,
103 | `confluence://spaces/${spaceKey}/pages`
104 | ];
105 |
106 | for (const uri of resourceUris) {
107 | try {
108 | console.log(`\nResource: ${uri}`);
109 | const res = await client.readResource({ uri });
110 | if (uri === "confluence://spaces") {
111 | const spacesData = JSON.parse(String(res.contents[0].text));
112 | console.log("Number of spaces:", spacesData.spaces?.length || 0);
113 | } else if (uri.includes("/pages")) {
114 | const pagesData = JSON.parse(String(res.contents[0].text));
115 | console.log("Number of pages:", pagesData.pages?.length || 0);
116 | }
117 | printResourceMetaAndSchema(res);
118 | } catch (e) {
119 | console.error(`Resource ${uri} error:`, e instanceof Error ? e.message : e);
120 | }
121 | }
122 |
123 | console.log("\n=== Finished testing Confluence Spaces Resource! ===");
124 | await client.close();
125 | }
126 |
127 | main();
```
--------------------------------------------------------------------------------
/src/tools/confluence/add-comment.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { z } from 'zod';
2 | import { callConfluenceApi } from '../../utils/atlassian-api-base.js';
3 | import { AtlassianConfig } from '../../utils/atlassian-api-base.js';
4 | import { ApiError, ApiErrorType } from '../../utils/error-handler.js';
5 | import { Logger } from '../../utils/logger.js';
6 | import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
7 | import { McpResponse, createSuccessResponse, createErrorResponse } from '../../utils/mcp-core.js';
8 | import { addConfluenceCommentV2 } from '../../utils/confluence-tool-api.js';
9 | import { Config, Tools } from '../../utils/mcp-helpers.js';
10 |
11 | // Initialize logger
12 | const logger = Logger.getLogger('ConfluenceTools:addComment');
13 |
14 | // Input parameter schema
15 | export const addCommentSchema = z.object({
16 | pageId: z.string().describe('ID of the page to add a comment to'),
17 | content: z.string().describe('Content of the comment (Confluence storage format, XML-like HTML)')
18 | });
19 |
20 | type AddCommentParams = z.infer<typeof addCommentSchema>;
21 |
22 | interface AddCommentResult {
23 | id: string;
24 | created: string;
25 | author: string;
26 | body: string;
27 | success: boolean;
28 | }
29 |
30 | // Main handler to add a comment to a page (API v2)
31 | export async function addCommentHandler(
32 | params: AddCommentParams,
33 | config: AtlassianConfig
34 | ): Promise<AddCommentResult> {
35 | try {
36 | logger.info(`Adding comment (v2) to page: ${params.pageId}`);
37 | const data = await addConfluenceCommentV2(config, {
38 | pageId: params.pageId,
39 | content: params.content
40 | });
41 | return {
42 | id: data.id,
43 | created: data.createdAt,
44 | author: data.createdBy?.displayName || '',
45 | body: data.body?.value || '',
46 | success: true
47 | };
48 | } catch (error) {
49 | if (error instanceof ApiError) {
50 | throw error;
51 | }
52 | logger.error(`Error adding comment (v2) to page ${params.pageId}:`, error);
53 | let message = `Failed to add comment: ${error instanceof Error ? error.message : String(error)}`;
54 | throw new ApiError(
55 | ApiErrorType.SERVER_ERROR,
56 | message,
57 | 500
58 | );
59 | }
60 | }
61 |
62 | // Register the tool with MCP Server
63 | export const registerAddCommentTool = (server: McpServer) => {
64 | server.tool(
65 | 'addComment',
66 | 'Add a comment to a Confluence page',
67 | addCommentSchema.shape,
68 | async (params: AddCommentParams, context: Record<string, any>) => {
69 | try {
70 | const config = context?.atlassianConfig ?? Config.getAtlassianConfigFromEnv();
71 | if (!config) {
72 | return {
73 | content: [
74 | { type: 'text', text: 'Invalid or missing Atlassian configuration' }
75 | ],
76 | isError: true
77 | };
78 | }
79 | const result = await addCommentHandler(params, config);
80 | return {
81 | content: [
82 | {
83 | type: 'text',
84 | text: JSON.stringify({
85 | success: true,
86 | message: `Comment added successfully with ID: ${result.id}`,
87 | id: result.id,
88 | created: result.created,
89 | author: result.author,
90 | body: result.body
91 | })
92 | }
93 | ]
94 | };
95 | } catch (error) {
96 | if (error instanceof ApiError) {
97 | return {
98 | content: [
99 | {
100 | type: 'text',
101 | text: JSON.stringify({
102 | success: false,
103 | message: error.message,
104 | code: error.code,
105 | statusCode: error.statusCode,
106 | type: error.type
107 | })
108 | }
109 | ],
110 | isError: true
111 | };
112 | }
113 | return {
114 | content: [
115 | {
116 | type: 'text',
117 | text: JSON.stringify({
118 | success: false,
119 | message: `Error while adding comment: ${error instanceof Error ? error.message : String(error)}`
120 | })
121 | }
122 | ],
123 | isError: true
124 | };
125 | }
126 | }
127 | );
128 | };
```
--------------------------------------------------------------------------------
/src/utils/confluence-tool-api.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { AtlassianConfig } from './atlassian-api-base.js';
2 | import { callConfluenceApi } from './atlassian-api-base.js';
3 |
4 | // Create a new Confluence page (API v2)
5 | export async function createConfluencePageV2(config: AtlassianConfig, params: { spaceId: string, title: string, content: string, parentId?: string }): Promise<any> {
6 | // Validate content
7 | if (!params.content.trim().startsWith('<')) {
8 | throw new Error('Content must be in Confluence storage format (XML-like HTML).');
9 | }
10 | // Chuẩn bị payload
11 | const requestData: any = {
12 | spaceId: params.spaceId,
13 | title: params.title,
14 | body: {
15 | representation: 'storage',
16 | value: params.content
17 | }
18 | };
19 | if (params.parentId) requestData.parentId = params.parentId;
20 | // Gọi API tạo page
21 | return await callConfluenceApi<any>(
22 | config,
23 | `/api/v2/pages`,
24 | 'POST',
25 | requestData
26 | );
27 | }
28 |
29 | // Update a Confluence page (API v2)
30 | // Có thể update title hoặc body hoặc cả hai. Mỗi lần update phải tăng version.
31 | export async function updateConfluencePageV2(config: AtlassianConfig, params: { pageId: string, title: string, content: string, version: number }): Promise<any> {
32 | const payload = {
33 | id: params.pageId,
34 | status: "current",
35 | title: params.title,
36 | body: {
37 | representation: "storage",
38 | value: params.content
39 | },
40 | version: {
41 | number: params.version
42 | }
43 | };
44 | return await callConfluenceApi<any>(
45 | config,
46 | `/api/v2/pages/${encodeURIComponent(params.pageId)}`,
47 | 'PUT',
48 | payload
49 | );
50 | }
51 |
52 | // Add a comment to a Confluence page (API v2)
53 | export async function addConfluenceCommentV2(config: AtlassianConfig, params: { pageId: string, content: string }): Promise<any> {
54 | if (!params.content.trim().startsWith('<')) {
55 | throw new Error('Comment content must be in Confluence storage format (XML-like HTML).');
56 | }
57 | const requestData = {
58 | pageId: params.pageId,
59 | body: {
60 | representation: 'storage',
61 | value: params.content
62 | }
63 | };
64 | return await callConfluenceApi<any>(
65 | config,
66 | `/api/v2/footer-comments`,
67 | 'POST',
68 | requestData
69 | );
70 | }
71 |
72 | // Delete a Confluence page (API v2)
73 | export async function deleteConfluencePageV2(config: AtlassianConfig, params: { pageId: string, draft?: boolean, purge?: boolean }): Promise<any> {
74 | const query: string[] = [];
75 | if (params.draft) query.push('draft=true');
76 | if (params.purge) query.push('purge=true');
77 | const endpoint = `/api/v2/pages/${encodeURIComponent(params.pageId)}` + (query.length ? `?${query.join('&')}` : '');
78 | return await callConfluenceApi<any>(
79 | config,
80 | endpoint,
81 | 'DELETE'
82 | );
83 | }
84 |
85 | // Update Confluence page title (API v2)
86 | export async function updateConfluencePageTitleV2(config: AtlassianConfig, params: { pageId: string, title: string, version: number }): Promise<any> {
87 | const payload = {
88 | title: params.title,
89 | version: { number: params.version },
90 | status: "current"
91 | };
92 | return await callConfluenceApi<any>(
93 | config,
94 | `/api/v2/pages/${encodeURIComponent(params.pageId)}/title`,
95 | 'PUT',
96 | payload
97 | );
98 | }
99 |
100 | // Update a footer comment in Confluence (API v2)
101 | export async function updateConfluenceFooterCommentV2(config: AtlassianConfig, params: { commentId: string|number, version: number, value: string, representation?: string, message?: string }): Promise<any> {
102 | const payload: any = {
103 | version: {
104 | number: params.version
105 | },
106 | body: {
107 | representation: params.representation || 'storage',
108 | value: params.value
109 | }
110 | };
111 | if (params.message) payload.version.message = params.message;
112 | return await callConfluenceApi<any>(
113 | config,
114 | `/api/v2/footer-comments/${encodeURIComponent(params.commentId)}`,
115 | 'PUT',
116 | payload
117 | );
118 | }
119 |
120 | // Delete a footer comment in Confluence (API v2)
121 | export async function deleteConfluenceFooterCommentV2(config: AtlassianConfig, commentId: string|number): Promise<any> {
122 | return await callConfluenceApi<any>(
123 | config,
124 | `/api/v2/footer-comments/${encodeURIComponent(commentId)}`,
125 | 'DELETE'
126 | );
127 | }
```
--------------------------------------------------------------------------------
/dev_mcp-atlassian-test-client/src/test-jira-users.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
3 | import path from 'path';
4 | import { fileURLToPath } from "url";
5 | import fs from "fs";
6 |
7 | // Get current file path
8 | const __filename = fileURLToPath(import.meta.url);
9 | const __dirname = path.dirname(__filename);
10 |
11 | // Load environment variables from .env
12 | function loadEnv(): Record<string, string> {
13 | try {
14 | const envFile = path.resolve(process.cwd(), '.env');
15 | const envContent = fs.readFileSync(envFile, 'utf8');
16 | const envVars: Record<string, string> = {};
17 | envContent.split('\n').forEach(line => {
18 | if (line.trim().startsWith('#') || !line.trim()) return;
19 | const [key, ...valueParts] = line.split('=');
20 | if (key && valueParts.length > 0) {
21 | const value = valueParts.join('=');
22 | envVars[key.trim()] = value.trim();
23 | }
24 | });
25 | return envVars;
26 | } catch (error) {
27 | console.error("Error loading .env file:", error);
28 | return {};
29 | }
30 | }
31 |
32 | // Print metadata and schema
33 | function printResourceMetaAndSchema(res: any) {
34 | if (res.contents && res.contents.length > 0) {
35 | const content = res.contents[0];
36 | // Print metadata if exists
37 | if (content.metadata) {
38 | console.log("Metadata:", content.metadata);
39 | }
40 | // Print schema if exists
41 | if (content.schema) {
42 | console.log("Schema:", JSON.stringify(content.schema, null, 2));
43 | }
44 | // Try to parse text if exists
45 | if (content.text) {
46 | try {
47 | const data = JSON.parse(String(content.text));
48 | if (Array.isArray(data)) {
49 | console.log("Data (array, first element):", data[0]);
50 | } else if (typeof data === 'object') {
51 | console.log("Data (object):", data);
52 | } else {
53 | console.log("Data:", data);
54 | }
55 | } catch {
56 | console.log("Cannot parse text.");
57 | }
58 | }
59 | }
60 | }
61 |
62 | async function main() {
63 | const client = new Client({
64 | name: "mcp-atlassian-test-client-jira-users",
65 | version: "1.0.0"
66 | });
67 |
68 | // Path to MCP server
69 | const serverPath = "/opt/homebrew/lib/node_modules/@phuc-nt/mcp-atlassian-server/dist/index.js";
70 |
71 | // Load environment variables
72 | const envVars = loadEnv();
73 | const processEnv: Record<string, string> = {};
74 | Object.keys(process.env).forEach(key => {
75 | if (process.env[key] !== undefined) {
76 | processEnv[key] = process.env[key] as string;
77 | }
78 | });
79 |
80 | // Initialize transport
81 | const transport = new StdioClientTransport({
82 | command: "node",
83 | args: [serverPath],
84 | env: {
85 | ...processEnv,
86 | ...envVars
87 | }
88 | });
89 |
90 | // Connect to server
91 | console.log("Connecting to MCP server...");
92 | await client.connect(transport);
93 |
94 | console.log("\n=== Test Jira Users Resource ===");
95 |
96 | // Change these values to match your environment if needed
97 | const accountId = "557058:24acce7b-a0c1-4f45-97f1-7eb4afd2ff5f";
98 | const projectKey = "XDEMO2";
99 | const roleId = "10002"; // Example roleId, get the correct one from project roles
100 |
101 | // NOTE: Resource jira://users has been removed because it requires query parameters
102 | // (username or accountId) and cannot be accessed directly without parameters.
103 | // We only test more specific resources below.
104 |
105 | const resourceUris = [
106 | `jira://users/${accountId}`,
107 | `jira://users/assignable/${projectKey}`,
108 | `jira://users/role/${projectKey}/${roleId}`
109 | ];
110 |
111 | for (const uri of resourceUris) {
112 | try {
113 | console.log(`\nResource: ${uri}`);
114 | const res = await client.readResource({ uri });
115 | if (uri.startsWith("jira://users/")) {
116 | const userData = JSON.parse(String(res.contents[0].text));
117 | if (userData.user) {
118 | console.log("User:", {
119 | accountId: userData.user.accountId,
120 | displayName: userData.user.displayName,
121 | emailAddress: userData.user.emailAddress,
122 | active: userData.user.active
123 | });
124 | } else if (userData.users && userData.users.length > 0) {
125 | console.log("Number of users:", userData.users.length);
126 | }
127 | }
128 | printResourceMetaAndSchema(res);
129 | } catch (e) {
130 | console.error(`Resource ${uri} error:`, e instanceof Error ? e.message : e);
131 | }
132 | }
133 |
134 | console.log("\n=== Finished testing Jira Users Resource! ===");
135 | await client.close();
136 | }
137 |
138 | main();
```
--------------------------------------------------------------------------------
/docs/dev-guide/advance-resource-tool.md:
--------------------------------------------------------------------------------
```markdown
1 | # Hướng Dẫn Implementation: Bổ Sung Resources và Tools cho MCP Atlassian Server (Chuẩn hóa atlassian-api.ts)
2 |
3 | Dưới đây là hướng dẫn chi tiết để bổ sung resource/tool mới cho MCP Atlassian Server, **chỉ sử dụng các hàm từ `atlassian-api.ts`** (KHÔNG sử dụng `jiraClient`, `confluenceClient` hay `atlassian-client.js`).
4 |
5 | ---
6 |
7 | ## I. Bổ Sung Resources
8 |
9 | ### 1. Jira: Filters
10 | ```typescript
11 | import { getFilters, getFilterById, getMyFilters } from '../../utils/atlassian-api.js';
12 |
13 | // Danh sách filter
14 | const response = await getFilters(config, offset, limit);
15 | // Chi tiết filter
16 | const filter = await getFilterById(config, filterId);
17 | // Filter cá nhân
18 | const myFilters = await getMyFilters(config);
19 | ```
20 |
21 | ### 2. Jira: Boards
22 | ```typescript
23 | import { getBoards, getBoardById, getBoardIssues } from '../../utils/atlassian-api.js';
24 |
25 | // Danh sách boards
26 | const response = await getBoards(config, offset, limit);
27 | // Chi tiết board
28 | const board = await getBoardById(config, boardId);
29 | // Issues trong board
30 | const issues = await getBoardIssues(config, boardId, offset, limit);
31 | ```
32 |
33 | ### 3. Jira: Sprints
34 | ```typescript
35 | import { getSprintsByBoard, getSprintById, getSprintIssues } from '../../utils/atlassian-api.js';
36 |
37 | // Danh sách sprints trong board
38 | const response = await getSprintsByBoard(config, boardId, offset, limit);
39 | // Chi tiết sprint
40 | const sprint = await getSprintById(config, sprintId);
41 | // Issues trong sprint
42 | const issues = await getSprintIssues(config, sprintId, offset, limit);
43 | ```
44 |
45 | ### 4. Confluence: Labels, Attachments, Content Versions
46 | ```typescript
47 | import { getPageLabels, getPageAttachments, getPageVersions } from '../../utils/atlassian-api.js';
48 |
49 | // Labels của page
50 | const response = await getPageLabels(config, pageId, offset, limit);
51 | // Attachments của page
52 | const response = await getPageAttachments(config, pageId, offset, limit);
53 | // Versions của page
54 | const response = await getPageVersions(config, pageId, offset, limit);
55 | ```
56 |
57 | ---
58 |
59 | ## II. Bổ Sung Tools
60 |
61 | ### 1. Filter Tools
62 | ```typescript
63 | import { createFilter, updateFilter, deleteFilter } from '../../utils/atlassian-api.js';
64 |
65 | // Tạo filter
66 | const response = await createFilter(config, name, jql, description, favourite);
67 | // Cập nhật filter
68 | const response = await updateFilter(config, filterId, { name, jql, description, favourite });
69 | // Xóa filter
70 | await deleteFilter(config, filterId);
71 | ```
72 |
73 | ### 2. Sprint Tools
74 | ```typescript
75 | import { createSprint } from '../../utils/atlassian-api.js';
76 |
77 | // Tạo sprint
78 | const response = await createSprint(config, boardId, name, startDate, endDate, goal);
79 | ```
80 |
81 | ### 3. Confluence Label Tools
82 | ```typescript
83 | import { addLabelsToPage, removeLabelsFromPage } from '../../utils/atlassian-api.js';
84 |
85 | // Thêm label vào page
86 | await addLabelsToPage(config, pageId, labels);
87 | // Xóa label khỏi page
88 | await removeLabelsFromPage(config, pageId, labels);
89 | ```
90 |
91 | ---
92 |
93 | ## III. Lưu ý Quan Trọng
94 |
95 | 1. **Chỉ sử dụng các hàm từ `atlassian-api.ts`** để thao tác với Jira/Confluence. KHÔNG sử dụng `jiraClient`, `confluenceClient`, hoặc bất kỳ client wrapper JS nào khác.
96 | 2. **API Endpoints**: Jira Agile API sử dụng `/rest/agile/1.0/`, Jira core sử dụng `/rest/api/3/`, Confluence sử dụng `/wiki/rest/api/` (các hàm trong `atlassian-api.ts` đã chuẩn hóa sẵn).
97 | 3. **Xử lý ADF**: Sử dụng hàm `adfToMarkdown` trong `atlassian-api.ts` nếu cần chuyển đổi nội dung rich text.
98 | 4. **Phân trang**: Các hàm resource đều hỗ trợ `offset`, `limit`.
99 | 5. **Schema**: Đảm bảo resource/tool trả về đúng schema đã định nghĩa.
100 | 6. **Error Handling**: Sử dụng try/catch và trả về lỗi rõ ràng.
101 |
102 | ---
103 |
104 | ## IV. Ví dụ tổng quát
105 |
106 | ```typescript
107 | // Lấy danh sách filter
108 | const filters = await getFilters(config, 0, 20);
109 |
110 | // Tạo filter mới
111 | const newFilter = await createFilter(config, 'My Filter', 'project = TEST', 'Test filter', true);
112 |
113 | // Thêm label vào page Confluence
114 | await addLabelsToPage(config, '123456', ['important', 'urgent']);
115 | ```
116 |
117 | ---
118 |
119 | **Tóm lại:**
120 | - Luôn import và sử dụng các hàm từ `atlassian-api.ts`.
121 | - Không còn bất kỳ import hoặc hướng dẫn nào liên quan đến `atlassian-client.js`, `jiraClient`, `confluenceClient`.
122 | - Nếu cần mở rộng resource/tool mới, hãy viết hàm mới trong `atlassian-api.ts` rồi sử dụng lại ở resource/tool.
123 |
124 | Với hướng dẫn này, bạn có thể triển khai đầy đủ các resource và tool mới cho MCP Atlassian Server, giúp người dùng tương tác hiệu quả hơn với Jira và Confluence thông qua AI.
```
--------------------------------------------------------------------------------
/src/tests/confluence/create-page.test.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { createPageHandler } from '../../tools/confluence/create-page.js';
2 | import { callConfluenceApi } from '../../utils/atlassian-api-base.js';
3 | import { ApiError, ApiErrorType } from '../../utils/error-handler.js';
4 |
5 | // Mock cho các dependencies
6 | jest.mock('../../utils/atlassian-api.js');
7 | jest.mock('../../utils/logger.js', () => ({
8 | Logger: {
9 | getLogger: jest.fn().mockReturnValue({
10 | info: jest.fn(),
11 | error: jest.fn(),
12 | debug: jest.fn(),
13 | warn: jest.fn()
14 | })
15 | }
16 | }));
17 |
18 | describe('createPageHandler', () => {
19 | // Thiết lập mock và cấu hình trước mỗi test
20 | beforeEach(() => {
21 | jest.clearAllMocks();
22 | });
23 |
24 | const mockConfig = {
25 | baseUrl: 'https://test.atlassian.net',
26 | apiToken: 'test-token',
27 | email: '[email protected]'
28 | };
29 |
30 | const mockPageResponse = {
31 | id: '12345',
32 | type: 'page',
33 | status: 'current',
34 | title: 'Test Page Title',
35 | space: {
36 | key: 'TESTSPACE',
37 | name: 'Test Space'
38 | },
39 | _links: {
40 | webui: '/spaces/TESTSPACE/pages/12345',
41 | self: '/rest/api/content/12345'
42 | }
43 | };
44 |
45 | test('should create page successfully', async () => {
46 | // Arrange
47 | const mockParams = {
48 | spaceId: 'TEST',
49 | title: 'Test Page Title',
50 | content: '<p>This is test content</p>'
51 | };
52 |
53 | (callConfluenceApi as jest.Mock).mockResolvedValue(mockPageResponse);
54 |
55 | // Act
56 | const result = await createPageHandler(mockParams, mockConfig);
57 |
58 | // Assert
59 | expect(callConfluenceApi).toHaveBeenCalledWith(
60 | mockConfig,
61 | '/content',
62 | 'POST',
63 | expect.objectContaining({
64 | type: 'page',
65 | title: 'Test Page Title',
66 | space: { key: 'TESTSPACE' },
67 | body: {
68 | storage: {
69 | value: '<p>This is test content</p>',
70 | representation: 'storage'
71 | }
72 | }
73 | })
74 | );
75 |
76 | expect(result).toEqual({
77 | id: '12345',
78 | type: 'page',
79 | status: 'current',
80 | title: 'Test Page Title',
81 | spaceKey: 'TESTSPACE',
82 | _links: {
83 | webui: '/spaces/TESTSPACE/pages/12345',
84 | self: '/rest/api/content/12345'
85 | },
86 | success: true
87 | });
88 | });
89 |
90 | test('should create page with parent ID', async () => {
91 | // Arrange
92 | const mockParams = {
93 | spaceId: 'TEST',
94 | title: 'Child Page Title',
95 | content: '<p>This is child page content</p>',
96 | parentId: '98765'
97 | };
98 |
99 | (callConfluenceApi as jest.Mock).mockResolvedValue({
100 | ...mockPageResponse,
101 | title: 'Child Page Title'
102 | });
103 |
104 | // Act
105 | const result = await createPageHandler(mockParams, mockConfig);
106 |
107 | // Assert
108 | expect(callConfluenceApi).toHaveBeenCalledWith(
109 | mockConfig,
110 | '/content',
111 | 'POST',
112 | expect.objectContaining({
113 | ancestors: [{ id: '98765' }]
114 | })
115 | );
116 |
117 | expect(result.title).toBe('Child Page Title');
118 | });
119 |
120 | test('should create page with labels', async () => {
121 | // Arrange
122 | const mockParams = {
123 | spaceId: 'TEST',
124 | title: 'Labeled Page',
125 | content: '<p>This page has labels</p>',
126 | labels: ['test', 'documentation']
127 | };
128 |
129 | (callConfluenceApi as jest.Mock)
130 | .mockResolvedValueOnce({
131 | ...mockPageResponse,
132 | title: 'Labeled Page'
133 | })
134 | .mockResolvedValueOnce({}); // Response for adding labels
135 |
136 | // Act
137 | const result = await createPageHandler(mockParams, mockConfig);
138 |
139 | // Assert
140 | // Kiểm tra cuộc gọi đầu tiên để tạo trang
141 | expect(callConfluenceApi).toHaveBeenNthCalledWith(
142 | 1,
143 | mockConfig,
144 | '/content',
145 | 'POST',
146 | expect.any(Object)
147 | );
148 |
149 | // Kiểm tra cuộc gọi thứ hai để thêm labels
150 | expect(callConfluenceApi).toHaveBeenNthCalledWith(
151 | 2,
152 | mockConfig,
153 | '/content/12345/label',
154 | 'POST',
155 | [{ name: 'test' }, { name: 'documentation' }]
156 | );
157 |
158 | expect(result.title).toBe('Labeled Page');
159 | expect(result.success).toBe(true);
160 | });
161 |
162 | test('should handle API error', async () => {
163 | // Arrange
164 | const mockParams = {
165 | spaceId: 'INVALID',
166 | title: 'Error Page',
167 | content: '<p>This will cause an error</p>'
168 | };
169 |
170 | const mockError = new ApiError(ApiErrorType.SERVER_ERROR, 'Simulated error');
171 | (callConfluenceApi as jest.Mock).mockRejectedValue(mockError);
172 |
173 | // Act & Assert
174 | await expect(createPageHandler(mockParams, mockConfig)).rejects.toThrow(mockError);
175 | });
176 | });
```
--------------------------------------------------------------------------------
/docs/dev-guide/mcp-client-for-testing.md:
--------------------------------------------------------------------------------
```markdown
1 | Để tạo một MCP Client đơn giản để test các thay đổi của MCP server trước khi test với Cline, bạn có thể làm theo các bước sau:
2 |
3 | ## Tạo MCP Test Client đơn giản
4 |
5 | ### 1. Cài đặt SDK
6 | ```bash
7 | npm install @modelcontextprotocol/sdk
8 | ```
9 |
10 | ### 2. Tạo file client.js (hoặc client.ts)
11 | ```javascript
12 | import { Client, Implementation } from "@modelcontextprotocol/sdk/client/mcp.js";
13 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
14 | import { spawn } from "child_process";
15 |
16 | // Đường dẫn đến MCP server của bạn
17 | const SERVER_PATH = "./dist/index.js";
18 |
19 | async function main() {
20 | try {
21 | // Khởi tạo process cho server
22 | const process = spawn("node", [SERVER_PATH]);
23 |
24 | // Tạo transport kết nối với server qua stdio
25 | const transport = new StdioClientTransport({
26 | input: process.stdout,
27 | output: process.stdin
28 | });
29 |
30 | // Tạo MCP client
31 | const client = new Client(
32 | new Implementation("test-client", "1.0.0")
33 | );
34 |
35 | // Kết nối với server
36 | await client.connect(transport);
37 | console.log("Connected to MCP server");
38 |
39 | // Liệt kê các resources
40 | const resources = await client.listResources();
41 | console.log("Available resources:", resources?.resources?.map(r => r.uri) || []);
42 |
43 | // Liệt kê các tools
44 | const tools = await client.listTools();
45 | console.log("Available tools:", tools?.tools?.map(t => t.name) || []);
46 |
47 | // Test một resource (ví dụ: jira://issues)
48 | if (resources?.resources?.some(r => r.uri === "jira://issues")) {
49 | const result = await client.readResource("jira://issues");
50 | console.log("Resource result:", JSON.parse(result.contents[0].text));
51 | }
52 |
53 | // Test một tool (ví dụ: createIssue)
54 | if (tools?.tools?.some(t => t.name === "createIssue")) {
55 | const result = await client.callTool("createIssue", {
56 | projectKey: "DEMO",
57 | summary: "Test issue from MCP client"
58 | });
59 | console.log("Tool result:", result);
60 | }
61 |
62 | // Đóng kết nối
63 | await client.close();
64 | process.kill();
65 |
66 | } catch (error) {
67 | console.error("Error:", error);
68 | }
69 | }
70 |
71 | main();
72 | ```
73 |
74 | ### 3. Tạo script test có tham số
75 |
76 | Bạn có thể tạo một script test linh hoạt hơn, cho phép truyền tham số:
77 |
78 | ```javascript
79 | import { Client, Implementation } from "@modelcontextprotocol/sdk/client/mcp.js";
80 | import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
81 | import { spawn } from "child_process";
82 |
83 | async function testResource(client, resourceUri) {
84 | try {
85 | const result = await client.readResource(resourceUri);
86 | console.log(`Resource ${resourceUri} result:`, JSON.parse(result.contents[0].text));
87 | } catch (error) {
88 | console.error(`Error reading resource ${resourceUri}:`, error);
89 | }
90 | }
91 |
92 | async function testTool(client, toolName, params) {
93 | try {
94 | const result = await client.callTool(toolName, params);
95 | console.log(`Tool ${toolName} result:`, result);
96 | } catch (error) {
97 | console.error(`Error calling tool ${toolName}:`, error);
98 | }
99 | }
100 |
101 | async function main() {
102 | const serverPath = process.argv[2] || "./dist/index.js";
103 | const command = process.argv[3] || "list";
104 | const param = process.argv[4] || "";
105 | const jsonParams = process.argv[5] ? JSON.parse(process.argv[5]) : {};
106 |
107 | const process = spawn("node", [serverPath]);
108 | const transport = new StdioClientTransport({
109 | input: process.stdout,
110 | output: process.stdin
111 | });
112 |
113 | const client = new Client(new Implementation("test-client", "1.0.0"));
114 | await client.connect(transport);
115 |
116 | switch (command) {
117 | case "list":
118 | const resources = await client.listResources();
119 | console.log("Resources:", resources?.resources?.map(r => r.uri) || []);
120 | const tools = await client.listTools();
121 | console.log("Tools:", tools?.tools?.map(t => t.name) || []);
122 | break;
123 | case "resource":
124 | await testResource(client, param);
125 | break;
126 | case "tool":
127 | await testTool(client, param, jsonParams);
128 | break;
129 | default:
130 | console.log("Unknown command");
131 | }
132 |
133 | await client.close();
134 | process.kill();
135 | }
136 |
137 | main();
138 | ```
139 |
140 | ### 4. Chạy test client
141 |
142 | ```bash
143 | # Liệt kê tất cả resources và tools
144 | node client.js
145 |
146 | # Test một resource cụ thể
147 | node client.js ./dist/index.js resource "jira://issues"
148 |
149 | # Test một tool cụ thể với tham số
150 | node client.js ./dist/index.js tool "createIssue" '{"projectKey":"DEMO","summary":"Test issue"}'
151 | ```
152 |
153 | Client test này sẽ giúp bạn kiểm tra nhanh các thay đổi trong MCP server trước khi test với Cline, đặc biệt là khi bạn đang chuẩn hóa metadata và bổ sung schema cho các resource.
```
--------------------------------------------------------------------------------
/docs/test-reports/cline-installation-test-2025-05-04.md:
--------------------------------------------------------------------------------
```markdown
1 | # MCP Atlassian Server: Installation & Functionality Test Report
2 |
3 | ## Overview
4 |
5 | This report documents the testing of MCP Atlassian Server (by phuc-nt) when installed and used through Cline AI assistant. The test verifies the server's capabilities to interact with Atlassian Jira and Confluence, covering both Resource retrieval and Tool actions.
6 |
7 | ## Test Environment
8 |
9 | - **Client**: Cline AI assistant
10 | - **Installation Method**: Using llms-install.md guide
11 | - **MCP Server**: phuc-nt/mcp-atlassian-server
12 | - **Target Systems**: Atlassian Jira Cloud, Atlassian Confluence Cloud
13 | - **Test Date**: According to repository history
14 |
15 | ## Installation Test
16 |
17 | The server was successfully installed by instructing Cline to "Install MCP Atlassian Server (by phuc-nt)" following the llms-install.md guide. Cline was able to:
18 |
19 | 1. Interpret the installation instructions
20 | 2. Guide the user through configuration
21 | 3. Establish connection with the server
22 | 4. Successfully process commands through the MCP interface
23 |
24 | ## Functionality Tests
25 |
26 | ### Jira Resources & Tools
27 |
28 | | Feature | Test Case | Result | Details |
29 | |---------|-----------|--------|---------|
30 | | **Resource: Issues** | Retrieve issue transitions | ✅ Success | Retrieved transitions for XDEMO2-50 |
31 | | **Resource: Users** | List assignable users | ✅ Success | Retrieved 3 assignable users for project XDEMO2 |
32 | | **Tool: createIssue** | Create new issue | ✅ Success | Created issue XDEMO2-50 |
33 | | **Tool: updateIssue** | Update issue summary | ✅ Success | Changed summary to "MCP test updateIssue (auto)" |
34 | | **Tool: transitionIssue** | Change issue status | ✅ Success | Transitioned to "In Progress" using ID 11 |
35 | | **Tool: assignIssue** | Assign issue to user | ✅ Success | Assigned to user "LemmyC" |
36 |
37 | #### Sample Interaction - Creating Issue:
38 |
39 | ```
40 | Cline wants to use a tool on the `phuc-nt/mcp-atlassian-server` MCP server:
41 |
42 | createIssue
43 |
44 | Arguments
45 | {
46 | "issueIdOrKey": "XDEMO2-50",
47 | "summary": "MCP test updateIssue (auto)"
48 | }
49 |
50 | Response
51 | Issue XDEMO2-50 updated successfully
52 | ```
53 |
54 | ### Confluence Resources & Tools
55 |
56 | | Feature | Test Case | Result | Details |
57 | |---------|-----------|--------|---------|
58 | | **Resource: Spaces** | List available spaces | ✅ Success | Successfully retrieved space information |
59 | | **Tool: createPage** | Create new page | ✅ Success | Created "MCP test createPage (auto)" in space TX |
60 | | **Tool: addComment** | Add comment to page | ✅ Success | Added comment to page with ID 14843908 |
61 |
62 | #### Sample Interaction - Creating Page:
63 |
64 | ```
65 | Cline wants to use a tool on the `phuc-nt/mcp-atlassian-server` MCP server:
66 |
67 | createPage
68 |
69 | Arguments
70 | {
71 | "spaceKey": "TX",
72 | "title": "MCP test createPage (auto)",
73 | "content": "<h1>Test Page</h1>"
74 | }
75 |
76 | Response
77 | Page "MCP test createPage (auto)" created successfully in space TX.
78 | URL: https://phuc-nt.atlassian.net/wiki/spaces/TX/pages/14843908/MCP%2Btest%2BcreatePage%2B(auto)
79 | ```
80 |
81 | ## Test Results Summary
82 |
83 | All essential functionality of MCP Atlassian Server was successfully tested:
84 |
85 | ### Jira
86 | - Resource queries (projects, issues, users): ✅ Successful
87 | - Issue creation: ✅ Successful
88 | - Issue updates: ✅ Successful
89 | - Status transitions: ✅ Successful
90 | - Issue assignment: ✅ Successful
91 |
92 | ### Confluence
93 | - Resource queries (spaces): ✅ Successful
94 | - Page creation: ✅ Successful
95 | - Comment addition: ✅ Successful
96 |
97 | ## Notes for Users
98 |
99 | 1. **Installation Simplicity**: The server can be installed with a single command in Cline: "Install MCP Atlassian Server (by phuc-nt)", making it accessible even for users without technical expertise.
100 |
101 | 2. **Authentication Flow**: Cline will guide users through entering Atlassian credentials (site name, email, API token) during installation.
102 |
103 | 3. **Resource Usage Tips**:
104 | - When using transitions, first query the available transitions to get their IDs
105 | - For page operations in Confluence, note the page ID from URLs (format: .../pages/[ID]/...)
106 | - User assignment requires retrieving the accountId first
107 |
108 | 4. **Content Formatting**:
109 | - For Confluence pages, use simple HTML content initially
110 | - Keep JSON requests minimal with only required fields for best results
111 |
112 | 5. **Performance**: API requests were consistently fast, with response times suitable for interactive use.
113 |
114 | ## Conclusion
115 |
116 | MCP Atlassian Server (by phuc-nt) demonstrates reliable functionality when installed and used through Cline. The server successfully connects to both Jira and Confluence, providing a natural language interface to these tools. The integration works as expected, enabling users to perform common Atlassian tasks without leaving their AI assistant environment.
```
--------------------------------------------------------------------------------
/src/utils/confluence-resource-api.ts:
--------------------------------------------------------------------------------
```typescript
1 | import { AtlassianConfig } from './atlassian-api-base.js';
2 | import { callConfluenceApi } from './atlassian-api-base.js';
3 |
4 | // Get labels of a Confluence page (API v2, cursor-based)
5 | export async function getConfluencePageLabelsV2(config: AtlassianConfig, pageId: string, cursor?: string, limit: number = 25): Promise<any> {
6 | const params: Record<string, any> = { limit };
7 | if (cursor) params.cursor = cursor;
8 | return await callConfluenceApi<any>(
9 | config,
10 | `/api/v2/pages/${encodeURIComponent(pageId)}/labels`,
11 | 'GET',
12 | null,
13 | params
14 | );
15 | }
16 |
17 | // Get attachments of a Confluence page (API v2, cursor-based)
18 | export async function getConfluencePageAttachmentsV2(config: AtlassianConfig, pageId: string, cursor?: string, limit: number = 25): Promise<any> {
19 | const params: Record<string, any> = { limit };
20 | if (cursor) params.cursor = cursor;
21 | return await callConfluenceApi<any>(
22 | config,
23 | `/api/v2/pages/${encodeURIComponent(pageId)}/attachments`,
24 | 'GET',
25 | null,
26 | params
27 | );
28 | }
29 |
30 | // Get versions of a Confluence page (API v2, cursor-based)
31 | export async function getConfluencePageVersionsV2(config: AtlassianConfig, pageId: string, cursor?: string, limit: number = 25): Promise<any> {
32 | const params: Record<string, any> = { limit };
33 | if (cursor) params.cursor = cursor;
34 | return await callConfluenceApi<any>(
35 | config,
36 | `/api/v2/pages/${encodeURIComponent(pageId)}/versions`,
37 | 'GET',
38 | null,
39 | params
40 | );
41 | }
42 |
43 | // Get list of Confluence pages (API v2, cursor-based)
44 | export async function getConfluencePagesV2(config: AtlassianConfig, cursor?: string, limit: number = 25): Promise<any> {
45 | const params: Record<string, any> = { limit };
46 | if (cursor) params.cursor = cursor;
47 | return await callConfluenceApi<any>(
48 | config,
49 | `/api/v2/pages`,
50 | 'GET',
51 | null,
52 | params
53 | );
54 | }
55 |
56 | // Get Confluence page details (API v2, metadata only)
57 | export async function getConfluencePageV2(config: AtlassianConfig, pageId: string): Promise<any> {
58 | return await callConfluenceApi<any>(
59 | config,
60 | `/api/v2/pages/${encodeURIComponent(pageId)}`,
61 | 'GET'
62 | );
63 | }
64 |
65 | // Get Confluence page body (API v2)
66 | export async function getConfluencePageBodyV2(config: AtlassianConfig, pageId: string): Promise<any> {
67 | return await callConfluenceApi<any>(
68 | config,
69 | `/api/v2/pages/${encodeURIComponent(pageId)}/body`,
70 | 'GET'
71 | );
72 | }
73 |
74 | // Get Confluence page ancestors (API v2)
75 | export async function getConfluencePageAncestorsV2(config: AtlassianConfig, pageId: string): Promise<any> {
76 | return await callConfluenceApi<any>(
77 | config,
78 | `/api/v2/pages/${encodeURIComponent(pageId)}/ancestors`,
79 | 'GET'
80 | );
81 | }
82 |
83 | // Get list of Confluence spaces (API v2, cursor-based)
84 | export async function getConfluenceSpacesV2(config: AtlassianConfig, cursor?: string, limit: number = 25): Promise<any> {
85 | const params: Record<string, any> = { limit };
86 | if (cursor) params.cursor = cursor;
87 | return await callConfluenceApi<any>(
88 | config,
89 | `/api/v2/spaces`,
90 | 'GET',
91 | null,
92 | params
93 | );
94 | }
95 |
96 | // Get Confluence space details (API v2)
97 | export async function getConfluenceSpaceV2(config: AtlassianConfig, spaceKey: string): Promise<any> {
98 | return await callConfluenceApi<any>(
99 | config,
100 | `/api/v2/spaces/${encodeURIComponent(spaceKey)}`,
101 | 'GET'
102 | );
103 | }
104 |
105 | // Get children of a Confluence page (API v2)
106 | export async function getConfluencePageChildrenV2(config: AtlassianConfig, pageId: string): Promise<any> {
107 | return await callConfluenceApi<any>(
108 | config,
109 | `/api/v2/pages/${encodeURIComponent(pageId)}/children`,
110 | 'GET'
111 | );
112 | }
113 |
114 | // Get footer comments of a Confluence page (API v2)
115 | export async function getConfluencePageFooterCommentsV2(config: AtlassianConfig, pageId: string, params: { limit?: number, cursor?: string } = {}): Promise<any> {
116 | return await callConfluenceApi<any>(
117 | config,
118 | `/api/v2/pages/${encodeURIComponent(pageId)}/footer-comments`,
119 | 'GET',
120 | null,
121 | params
122 | );
123 | }
124 |
125 | // Get inline comments of a Confluence page (API v2)
126 | export async function getConfluencePageInlineCommentsV2(config: AtlassianConfig, pageId: string, params: { limit?: number, cursor?: string } = {}): Promise<any> {
127 | return await callConfluenceApi<any>(
128 | config,
129 | `/api/v2/pages/${encodeURIComponent(pageId)}/inline-comments`,
130 | 'GET',
131 | null,
132 | params
133 | );
134 | }
135 |
136 | // Get list of Confluence pages (API v2, hỗ trợ filter nâng cao)
137 | export async function getConfluencePagesWithFilters(config: AtlassianConfig, filters: Record<string, any> = {}): Promise<any> {
138 | return await callConfluenceApi<any>(
139 | config,
140 | '/api/v2/pages',
141 | 'GET',
142 | null,
143 | filters
144 | );
145 | }
```