UNPKG

mcp-integration-server-solin

Version:

MCP Server for Cody, YouTrack, and Forgejo integration

205 lines 7.98 kB
import axios from 'axios'; export class YouTrackService { client; baseUrl; token; constructor() { this.baseUrl = process.env.YOUTRACK_BASE_URL || ''; this.token = process.env.YOUTRACK_TOKEN || ''; if (!this.baseUrl || !this.token) { throw new Error('YouTrack 설정이 필요합니다: YOUTRACK_BASE_URL, YOUTRACK_TOKEN'); } this.client = axios.create({ baseURL: this.baseUrl, headers: { 'Authorization': `Bearer ${this.token}`, 'Accept': 'application/json', 'Content-Type': 'application/json', }, }); } async getIssues(args) { try { const { project, query = '', limit = 10 } = args; const searchQuery = query ? `project: ${project} ${query}` : `project: ${project}`; const response = await this.client.get('/api/issues', { params: { query: searchQuery, $top: limit, fields: 'id,idReadable,summary,description,created,updated,resolved,state(name),priority(name),type(name)', }, }); // YouTrack API 응답 구조 처리 let issuesData = response.data; // 응답이 객체이고 issues 프로퍼티가 있는 경우 if (issuesData && typeof issuesData === 'object' && !Array.isArray(issuesData)) { if (issuesData.issues) { issuesData = issuesData.issues; } else if (issuesData.value) { issuesData = issuesData.value; } else { // 단일 객체인 경우 배열로 변환 issuesData = [issuesData]; } } // 배열이 아닌 경우 빈 배열로 처리 if (!Array.isArray(issuesData)) { issuesData = []; } const issues = issuesData.map((issue) => ({ id: issue.idReadable, summary: issue.summary, description: issue.description, state: issue.state?.name, priority: issue.priority?.name, type: issue.type?.name, created: issue.created, updated: issue.updated, resolved: issue.resolved, })); return { content: [ { type: 'text', text: `YouTrack 이슈 목록 (${issues.length}개):\n\n${issues .map((issue) => `• ${issue.id}: ${issue.summary} [${issue.state}]`) .join('\n')}`, }, ], }; } catch (error) { throw new Error(`YouTrack 이슈 조회 실패: ${error instanceof Error ? error.message : String(error)}`); } } async getIssue(args) { try { const { issueId } = args; // 디버깅: 요청 정보 출력 console.log('YouTrack 요청 URL:', `${this.baseUrl}/api/issues/${issueId}`); console.log('YouTrack 인증 헤더:', this.client.defaults.headers['Authorization']); const response = await this.client.get(`/api/issues/${issueId}`, { params: { fields: 'id,idReadable,summary,description,created,updated,resolved,state(name),priority(name),type(name),assignee(name),reporter(name)', }, }); // 디버깅: 실제 응답 구조 확인 console.log('YouTrack API 응답:', JSON.stringify(response.data, null, 2)); const issue = response.data; return { content: [ { type: 'text', text: `YouTrack 이슈 상세정보: ID: ${issue.idReadable} 제목: ${issue.summary} 설명: ${issue.description || '설명 없음'} 상태: ${issue.state?.name || '알 수 없음'} 우선순위: ${issue.priority?.name || '알 수 없음'} 타입: ${issue.type?.name || '알 수 없음'} 담당자: ${issue.assignee?.name || '미할당'} 보고자: ${issue.reporter?.name || '알 수 없음'} 생성일: ${new Date(issue.created).toLocaleString()} 수정일: ${new Date(issue.updated).toLocaleString()}`, }, ], }; } catch (error) { throw new Error(`YouTrack 이슈 조회 실패: ${error instanceof Error ? error.message : String(error)}`); } } async createIssue(args) { try { const { project, summary, description, type = 'Task', priority = 'Normal' } = args; const issueData = { project: { id: project }, summary, description, }; const response = await this.client.post('/api/issues', issueData, { params: { fields: 'id,idReadable,summary', }, }); const issue = response.data; // 타입과 우선순위 설정 (별도 요청) if (type !== 'Task') { await this.updateIssueField(issue.idReadable, 'Type', type); } if (priority !== 'Normal') { await this.updateIssueField(issue.idReadable, 'Priority', priority); } return { content: [ { type: 'text', text: `YouTrack 이슈가 생성되었습니다: ${issue.idReadable} - ${issue.summary}`, }, ], }; } catch (error) { throw new Error(`YouTrack 이슈 생성 실패: ${error instanceof Error ? error.message : String(error)}`); } } async updateIssue(args) { try { const { issueId, summary, description, state } = args; const updates = {}; if (summary) updates.summary = summary; if (description) updates.description = description; if (Object.keys(updates).length > 0) { await this.client.post(`/api/issues/${issueId}`, updates); } if (state) { await this.updateIssueField(issueId, 'State', state); } return { content: [ { type: 'text', text: `YouTrack 이슈 ${issueId}가 업데이트되었습니다.`, }, ], }; } catch (error) { throw new Error(`YouTrack 이슈 업데이트 실패: ${error instanceof Error ? error.message : String(error)}`); } } async addComment(args) { try { const { issueId, comment } = args; await this.client.post(`/api/issues/${issueId}/comments`, { text: comment, }); return { content: [ { type: 'text', text: `YouTrack 이슈 ${issueId}에 댓글이 추가되었습니다.`, }, ], }; } catch (error) { throw new Error(`YouTrack 댓글 추가 실패: ${error instanceof Error ? error.message : String(error)}`); } } async updateIssueField(issueId, fieldName, value) { try { await this.client.post(`/api/issues/${issueId}/fields/${fieldName}`, { value: { name: value }, }); } catch (error) { console.error(`필드 ${fieldName} 업데이트 실패:`, error); } } } //# sourceMappingURL=youtrack.js.map