Skip to main content
Glama

MCP Atlassian Server

by phuc-nt
todo.md19.7 kB
# Kế hoạch Migration: Tái cấu trúc MCP Utils ## Checklist tiến độ migration (cập nhật mới nhất) ### Resource (Jira + Confluence) - [x] Tạo helpers mới: `mcp-core.ts`, `mcp-helpers.ts` - [x] Migrate toàn bộ resource Confluence: - [x] spaces.ts - [x] pages.ts (bao gồm migrate metadata từ content-metadata.ts) - [x] Xóa content-metadata.ts - [x] Migrate toàn bộ resource Jira: - [x] issues.ts - [x] projects.ts - [x] boards.ts - [x] sprints.ts - [x] filters.ts - [x] dashboards.ts - [x] users.ts - [x] Chuẩn hóa response, schema, context cho toàn bộ resource - [x] Đảm bảo backward compatibility (giữ file tạm, không đổi format response đột ngột) - [x] Test lại toàn bộ resource bằng test client, Cline, Cursor, ... - [x] Cập nhật docs hướng dẫn mở rộng resource/tool đúng chuẩn refactor mới ### Tool (Jira + Confluence) - [x] Migrate toàn bộ tool Jira sang helpers mới (`Tools` trong mcp-helpers.ts) - [x] Migrate toàn bộ tool Confluence sang helpers mới - [x] Chuẩn hóa response, schema, context cho toàn bộ tool Confluence - [x] Chuẩn hóa response, schema, context cho toàn bộ tool Jira - [x] Đảm bảo backward compatibility cho tool Confluence (giữ file tạm nếu cần) - [x] Đảm bảo backward compatibility cho tool Jira (giữ file tạm nếu cần) - [x] Test lại toàn bộ tool Confluence bằng test client - [x] Test lại toàn bộ tool Jira bằng test client - [x] Cập nhật docs hướng dẫn implement tool Confluence đúng chuẩn mới - [x] Cập nhật docs hướng dẫn implement tool Jira đúng chuẩn mới --- ## Hướng dẫn implement resource/tool (chuẩn mới) ### Resource - Luôn import `Config`, `Resources` từ `../../utils/mcp-helpers.js` - Không dùng hàm cũ từ mcp-resource.js, mcp-response.js - Đăng ký resource qua `server.resource()` với ResourceTemplate, callback chuẩn hóa: - Ưu tiên lấy config từ context nếu có, fallback về env: ```typescript const config = (extra && typeof extra === 'object' && 'context' in extra && extra.context && (extra.context as any).atlassianConfig) ? (extra.context as any).atlassianConfig : Config.getAtlassianConfigFromEnv(); ``` - Luôn trả về qua `Resources.createStandardResource(...)`, không trả về object tự do - Luôn có schema validate output, cập nhật schema nếu cần - Không đăng ký trùng URI pattern ở nhiều file - Không log credentials, không trả về thông tin nhạy cảm ### Tool - Luôn import `Tools` từ `../../utils/mcp-helpers.js` - Không dùng hàm cũ từ tool-helpers.js, mcp-response.js - Đăng ký tool qua `server.tool()` hoặc `Tools.registerTool()` với schema input rõ ràng, callback chuẩn hóa: - Ưu tiên lấy config từ context nếu có, fallback về env - Luôn trả về qua `Tools.createToolResponse(...)`, không trả về object tự do - Luôn có schema validate input/output, cập nhật schema nếu cần - Không log credentials, không trả về thông tin nhạy cảm --- ## Tiến độ thực tế - Đã migrate xong 100% resource (Jira + Confluence) và toàn bộ tool Confluence sang helpers mới, chuẩn hóa response, schema, context, giữ backward compatibility - Đã test lại toàn bộ resource/tool Confluence thành công - Tool Jira chưa migrate, cần thực hiện các bước checklist ở trên - Docs hướng dẫn implement resource/tool đã cập nhật đúng thực tế refactor mới (tham khảo docs/introduction/resources-and-tools.md) ## Thiết kế mới Thay vì 3 file hiện tại (`mcp-resource.ts`, `mcp-response.ts`, `tool-helpers.ts`), tái cấu trúc thành 2 module chính: 1. **`mcp-core.ts`**: Định nghĩa interfaces, types, và các hàm cơ bản để tạo responses 2. **`mcp-helpers.ts`**: Chứa các helper functions cho cả resources và tools, được tổ chức theo namespace ## Giai đoạn 1: Tạo cấu trúc mới ### 1.1. Tạo file `mcp-core.ts` ```typescript // src/utils/mcp-core.ts /** * Core interfaces and functions for MCP responses * This module provides the foundation for all MCP responses */ /** * Standard MCP response interface */ export interface McpResponse<T = any> { content: Array<McpContent>; isError?: boolean; data?: T; } /** * MCP content types */ export type McpContent = | { mimeType: string; text: string; [key: string]: unknown } | { mimeType: string; data: string; [key: string]: unknown }; /** * Create a standard response with JSON content */ export function createJsonResponse<T>(data: T, mimeType = 'application/json'): McpResponse<T> { return { content: [ { mimeType, text: JSON.stringify(data) } ], data }; } /** * Create a standard success response */ export function createSuccessResponse(message: string, data?: any): McpResponse { return createJsonResponse({ success: true, message, ...(data && { data }) }); } /** * Create a standard error response */ export function createErrorResponse(message: string, details?: any): McpResponse { return { content: [ { mimeType: 'application/json', text: JSON.stringify({ success: false, message, ...(details && { details }) }) } ], isError: true }; } ``` ### 1.2. Tạo file `mcp-helpers.ts` ```typescript // src/utils/mcp-helpers.ts /** * Helper functions for MCP resources and tools */ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import { McpResponse, createJsonResponse, createErrorResponse, createSuccessResponse } from './mcp-core.js'; import { ApiError, ApiErrorType } from './error-handler.js'; import { AtlassianConfig } from './atlassian-api-base.js'; import { Logger } from './logger.js'; import { StandardMetadata, createStandardMetadata } from '../schemas/common.js'; const logger = Logger.getLogger('MCPHelpers'); /** * Environment and configuration utilities */ export namespace Config { /** * Get Atlassian configuration from environment variables */ export function getAtlassianConfigFromEnv(): AtlassianConfig { const ATLASSIAN_SITE_NAME = process.env.ATLASSIAN_SITE_NAME || ''; const ATLASSIAN_USER_EMAIL = process.env.ATLASSIAN_USER_EMAIL || ''; const ATLASSIAN_API_TOKEN = process.env.ATLASSIAN_API_TOKEN || ''; if (!ATLASSIAN_SITE_NAME || !ATLASSIAN_USER_EMAIL || !ATLASSIAN_API_TOKEN) { logger.error('Missing Atlassian credentials in environment variables'); throw new Error('Missing Atlassian credentials in environment variables'); } return { baseUrl: ATLASSIAN_SITE_NAME.includes('.atlassian.net') ? `https://${ATLASSIAN_SITE_NAME}` : ATLASSIAN_SITE_NAME, email: ATLASSIAN_USER_EMAIL, apiToken: ATLASSIAN_API_TOKEN }; } /** * Helper to get Atlassian config from context or environment */ export function getConfigFromContextOrEnv(context: any): AtlassianConfig { if (context?.atlassianConfig) { return context.atlassianConfig; } return getAtlassianConfigFromEnv(); } } /** * Resource helper functions */ export namespace Resources { /** * Create a standardized resource response with metadata and schema */ export function createStandardResource( uri: string, data: any[], dataKey: string, schema: any, totalCount: number, limit: number, offset: number, uiUrl?: string ): McpResponse { // Create standard metadata const metadata = createStandardMetadata(totalCount, limit, offset, uri, uiUrl); // Create response data object const responseData: Record<string, any> = { metadata: metadata }; // Add the data with the specified key responseData[dataKey] = data; // Return formatted resource return { contents: [ { uri: uri, mimeType: "application/json", text: JSON.stringify(responseData), schema: schema } ] }; } /** * Extract paging parameters from resource URI or request */ export function extractPagingParams( params: any, defaultLimit: number = 20, defaultOffset: number = 0 ): { limit: number, offset: number } { let limit = defaultLimit; let offset = defaultOffset; if (params) { // Extract limit if (params.limit) { const limitParam = Array.isArray(params.limit) ? params.limit[0] : params.limit; const parsedLimit = parseInt(limitParam, 10); if (!isNaN(parsedLimit) && parsedLimit > 0) { limit = parsedLimit; } } // Extract offset if (params.offset) { const offsetParam = Array.isArray(params.offset) ? params.offset[0] : params.offset; const parsedOffset = parseInt(offsetParam, 10); if (!isNaN(parsedOffset) && parsedOffset >= 0) { offset = parsedOffset; } } } return { limit, offset }; } /** * Higher-order function that wraps a resource handler with error handling */ export function wrapWithErrorHandling<T, P>( resourceName: string, handler: (params: P) => Promise<T> ): (params: P) => Promise<T> { return async (params: P): Promise<T> => { try { return await handler(params); } catch (error) { logger.error(`Error in resource ${resourceName}:`, error); // Convert to ApiError if not already const apiError = error instanceof ApiError ? error : new ApiError( ApiErrorType.RESOURCE_ERROR, `Error processing resource ${resourceName}: ${error instanceof Error ? error.message : String(error)}`, 500, error instanceof Error ? error : new Error(String(error)) ); throw apiError; } }; } } /** * Tool helper functions */ export namespace Tools { /** * Standardized response structure for MCP tools */ export interface ToolResponse<T = any> { content: Array<{ mimeType: string; text: string; }>; isError?: boolean; } /** * Create a standardized response for MCP tools */ export function createToolResponse<T = any>(success: boolean, message?: string, data?: T): ToolResponse<T> { const response = { success, ...(message && { message }), ...(data && { data }) }; return { content: [ { mimeType: 'application/json', text: JSON.stringify(response) } ] }; } /** * Higher-order function to wrap a tool implementation with standardized error handling */ export function wrapWithErrorHandling<T, P>( toolName: string, handler: (params: P) => Promise<T> ): (params: P) => Promise<ToolResponse<T>> { return async (params: P): Promise<ToolResponse<T>> => { try { // Execute the handler const result = await handler(params); // Return successful response with data return createToolResponse<T>(true, `${toolName} executed successfully`, result); } catch (error) { // Log the error logger.error(`Error executing tool ${toolName}:`, error); // Create appropriate error message let errorMessage: string; if (error instanceof ApiError) { errorMessage = error.message; } else { errorMessage = error instanceof Error ? error.message : String(error); } // Return standardized error response return createToolResponse(false, errorMessage); } }; } /** * Register a tool with the MCP server */ export function registerTool<P = any, R = any>( server: McpServer, name: string, schema: Record<string, any>, handler: (params: P, context: any) => Promise<R>, useErrorWrapper: boolean = true ): void { logger.info(`Registering tool: ${name}`); const wrappedHandler = useErrorWrapper ? async (params: P, context: any): Promise<ToolResponse<R>> => { try { logger.debug(`Executing tool ${name} with params:`, params); const result = await handler(params, context); logger.debug(`Tool ${name} executed successfully`); return createToolResponse(true, `${name} completed successfully`, result); } catch (error: any) { logger.error(`Error in tool ${name}:`, error); // Convert to standardized response return createToolResponse(false, error instanceof Error ? error.message : String(error)); } } : handler; server.tool(name, schema, wrappedHandler as any); } /** * Create a simple tool response with text content * @deprecated Use createToolResponse(true, message) instead */ export function createSimpleToolResponse(text: string): ToolResponse { return { content: [{ type: 'text', text }] as any }; } /** * Create an error response * @deprecated Use createToolResponse(false, errorMessage) instead */ export function createErrorResponse(error: Error | string): ToolResponse { const message = error instanceof Error ? error.message : error; return { content: [{ type: 'text', text: `Error: ${message}` }] as any, isError: true }; } } ``` ## Giai đoạn 2: Chuyển đổi các file hiện có ### 2.1. Tạo file tạm để đảm bảo tương thích ngược ```typescript // src/utils/mcp-resource.ts (deprecated version) import { Resources, Config } from './mcp-helpers.js'; import { Logger } from './logger.js'; const logger = Logger.getLogger('MCPResource'); // Re-export for backward compatibility export const getAtlassianConfigFromEnv = Config.getAtlassianConfigFromEnv; export const createJsonResource = Resources.createJsonResource; export const createJsonResourceWithSchema = Resources.createJsonResourceWithSchema; export const createStandardResource = Resources.createStandardResource; export const extractPagingParams = Resources.extractPagingParams; // Log deprecation warning logger.warn('mcp-resource.ts is deprecated. Please use mcp-helpers.js (Resources namespace) instead.'); ``` ```typescript // src/utils/tool-helpers.ts (deprecated version) import { Tools } from './mcp-helpers.js'; import { Logger } from './logger.js'; const logger = Logger.getLogger('ToolHelpers'); // Re-export for backward compatibility export const createToolResponse = Tools.createToolResponse; export const createSimpleToolResponse = Tools.createSimpleToolResponse; export const createErrorResponse = Tools.createErrorResponse; export const wrapToolWithErrorHandling = Tools.wrapWithErrorHandling; export const registerTool = Tools.registerTool; // Log deprecation warning logger.warn('tool-helpers.ts is deprecated. Please use mcp-helpers.js (Tools namespace) instead.'); ``` ```typescript // src/utils/mcp-response.ts (deprecated version) import { createJsonResponse, createErrorResponse, createSuccessResponse } from './mcp-core.js'; import { Logger } from './logger.js'; const logger = Logger.getLogger('MCPResponse'); // Re-export for backward compatibility export interface McpResponse { content: Array<any>; [key: string]: unknown; _meta?: Record<string, unknown>; isError?: boolean; } export type McpContent = any; export const createTextResponse = (text: string, additionalProps = {}) => { logger.warn('createTextResponse is deprecated. Please use mcp-core.js createSuccessResponse instead.'); return { ...additionalProps, content: [ { type: "text", text } ] }; }; export { createJsonResponse, createErrorResponse, createSuccessResponse }; // Log deprecation warning logger.warn('mcp-response.ts is deprecated. Please use mcp-core.js instead.'); ``` ## Giai đoạn 3: Cập nhật các file sử dụng các module cũ ### 3.1. Cập nhật imports trong một số file chính Ví dụ cho một số file cần cập nhật (không cập nhật tất cả ngay lập tức): ```typescript // src/resources/jira/issues.ts // Thay đổi từ import { createStandardResource, extractPagingParams } from '../../utils/mcp-resource.js'; // Thành import { Resources } from '../../utils/mcp-helpers.js'; // Và cập nhật các lệnh gọi const { limit, offset } = Resources.extractPagingParams(params); return Resources.createStandardResource(...); ``` ```typescript // src/tools/jira/create-issue.ts // Thay đổi từ import { createErrorResponse } from '../../utils/mcp-response.js'; // Thành import { createErrorResponse } from '../../utils/mcp-core.js'; // Hoặc nếu sử dụng createToolResponse import { Tools } from '../../utils/mcp-helpers.js'; return Tools.createToolResponse(false, 'Error message'); ``` ## Giai đoạn 4: Kiểm thử và Đảm bảo Tương thích ### 4.1. Kiểm thử từng phần - Triển khai từng file mới và test riêng - Xác nhận các file tạm cho tương thích ngược hoạt động đúng - Kiểm tra type safety trong VS Code/TypeScript - Chạy integration test với MCP Test Client ### 4.2. Cập nhật tài liệu - Thêm hướng dẫn sử dụng module mới vào tài liệu phát triển - Cập nhật schema metadata example nếu cần - Thêm mục deprecation notice vào tài liệu cho các module cũ ## Giai đoạn 5: Cập nhật dần trong toàn bộ codebase ### 5.1. Lộ trình chuyển đổi dần dần - Sprint 1: Triển khai cấu trúc mới và cập nhật 20% resource/tool - Sprint 2: Cập nhật thêm 30% resource/tool còn lại - Sprint 3: Cập nhật 50% còn lại và xem xét xóa bỏ file tạm - Sprint 4: Xóa bỏ hoàn toàn các file tạm sau khi đã chuyển đổi xong ## Ước tính thời gian - Giai đoạn 1 (Tạo cấu trúc mới): 1-2 ngày - Giai đoạn 2 (File tạm tương thích ngược): 1 ngày - Giai đoạn 3 (Cập nhật một số file chính): 2-3 ngày - Giai đoạn 4 (Kiểm thử): 1-2 ngày - Giai đoạn 5 (Cập nhật toàn bộ codebase): 1-2 tuần ## Lợi ích 1. **Cấu trúc rõ ràng**: Phân chia rõ ràng theo chức năng, không theo đối tượng sử dụng 2. **Giảm trùng lặp**: Không có định nghĩa và logic trùng lặp 3. **Dễ mở rộng**: Thêm chức năng mới dễ dàng hơn trong cấu trúc namespace 4. **Type-safe**: Tất cả đều được định nghĩa type đầy đủ 5. **Dễ dàng học và sử dụng**: Developers chỉ cần biết 2 file thay vì 3+ file ## Rủi ro và giảm thiểu 1. **Rủi ro**: Thay đổi lớn có thể gây ra lỗi không mong muốn - **Giảm thiểu**: Triển khai từng bước, giữ tương thích ngược, kiểm thử kỹ lưỡng 2. **Rủi ro**: Thời gian chuyển đổi toàn bộ codebase có thể kéo dài - **Giảm thiểu**: Tập trung vào các file/module quan trọng trước, sau đó mới đến các file ít dùng 3. **Rủi ro**: Định nghĩa interface mới có thể không bao quát đủ use case - **Giảm thiểu**: Xem xét tất cả các trường hợp sử dụng hiện tại, đảm bảo mở rộng được trong tương lai ## Cleanup utils cũ - [x] Đã xoá hoàn toàn các file tạm/deprecated: mcp-resource.ts, mcp-response.ts, tool-helpers.ts - [x] Codebase đã sạch, chỉ còn lại helpers mới (mcp-core.ts, mcp-helpers.ts)

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/phuc-nt/mcp-atlassian-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server