Skip to main content
Glama
youtrack.ts14.4 kB
import axios, { AxiosInstance, AxiosResponse } from 'axios'; import { YouTrackApiConfig, YouTrackApiResponse, YouTrackListResponse, YouTrackUser, YouTrackProject, YouTrackIssue, YouTrackIssueComment, YouTrackIssueAttachment, YouTrackWorkItem, YouTrackAgileBoard, YouTrackSprint, YouTrackTag, YouTrackUserGroup, YouTrackCustomField, YouTrackCreateIssueRequest, YouTrackUpdateIssueRequest, YouTrackCreateCommentRequest, YouTrackCreateWorkItemRequest, YouTrackCreateProjectRequest, YouTrackSearchOptions, YouTrackPaginationOptions, YouTrackError } from '../types/index.js'; export class YouTrackClient { private client: AxiosInstance; private config: YouTrackApiConfig; constructor(config: YouTrackApiConfig) { this.config = { timeout: 30000, maxRetries: 3, debug: false, ...config }; this.client = axios.create({ baseURL: `${this.config.baseUrl}/api`, timeout: this.config.timeout, headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Cache-Control': 'no-cache', ...(this.config.token && { Authorization: `Bearer ${this.config.token}` }), // Cloudflare Access headers ...(process.env.CF_ACCESS_CLIENT_ID && { 'CF-Access-Client-Id': process.env.CF_ACCESS_CLIENT_ID }), ...(process.env.CF_ACCESS_CLIENT_SECRET && { 'CF-Access-Client-Secret': process.env.CF_ACCESS_CLIENT_SECRET }) } }); // Add request interceptor for basic auth if username/password provided if (this.config.username && this.config.password) { this.client.interceptors.request.use(config => { const auth = Buffer.from(`${this.config.username}:${this.config.password}`).toString('base64'); config.headers.Authorization = `Basic ${auth}`; return config; }); } // Add response interceptor for error handling this.client.interceptors.response.use( (response) => response, (error) => { if (this.config.debug) { console.error('YouTrack API Error:', error.response?.data || error.message); } throw error; } ); } private async request<T>( method: string, url: string, data?: any, params?: any ): Promise<YouTrackApiResponse<T>> { try { const response: AxiosResponse<T> = await this.client.request({ method, url, data, params }); return { data: response.data, status: response.status, statusText: response.statusText, headers: response.headers as Record<string, string> }; } catch (error: any) { const errorData = error.response?.data as YouTrackError; throw new Error( errorData?.error_description || errorData?.error || error.message || 'Unknown YouTrack API error' ); } } // User Management async getCurrentUser(fields?: string): Promise<YouTrackUser> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackUser>('GET', '/users/me', undefined, params); return response.data; } async getUser(userId: string, fields?: string): Promise<YouTrackUser> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackUser>('GET', `/users/${userId}`, undefined, params); return response.data; } async listUsers(options?: YouTrackSearchOptions): Promise<YouTrackUser[]> { const response = await this.request<YouTrackUser[]>('GET', '/users', undefined, options); return response.data; } async listGroups(options?: YouTrackPaginationOptions): Promise<YouTrackUserGroup[]> { const response = await this.request<YouTrackUserGroup[]>('GET', '/groups', undefined, options); return response.data; } async getGroup(groupId: string, fields?: string): Promise<YouTrackUserGroup> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackUserGroup>('GET', `/groups/${groupId}`, undefined, params); return response.data; } // Project Management async listProjects(options?: YouTrackSearchOptions): Promise<YouTrackProject[]> { const response = await this.request<YouTrackProject[]>('GET', '/admin/projects', undefined, options); return response.data; } async getProject(projectId: string, fields?: string): Promise<YouTrackProject> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackProject>('GET', `/admin/projects/${projectId}`, undefined, params); return response.data; } async createProject(projectData: YouTrackCreateProjectRequest): Promise<YouTrackProject> { const response = await this.request<YouTrackProject>('POST', '/admin/projects', projectData); return response.data; } async updateProject(projectId: string, projectData: Partial<YouTrackCreateProjectRequest>): Promise<YouTrackProject> { const response = await this.request<YouTrackProject>('POST', `/admin/projects/${projectId}`, projectData); return response.data; } async deleteProject(projectId: string): Promise<void> { await this.request('DELETE', `/admin/projects/${projectId}`); } async getProjectCustomFields(projectId: string, fields?: string): Promise<YouTrackCustomField[]> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackCustomField[]>( 'GET', `/admin/projects/${projectId}/customFields`, undefined, params ); return response.data; } // Issue Management async listIssues(options?: YouTrackSearchOptions): Promise<YouTrackIssue[]> { const response = await this.request<YouTrackIssue[]>('GET', '/issues', undefined, options); return response.data; } async getIssue(issueId: string, fields?: string): Promise<YouTrackIssue> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackIssue>('GET', `/issues/${issueId}`, undefined, params); return response.data; } async createIssue(issueData: YouTrackCreateIssueRequest): Promise<YouTrackIssue> { const response = await this.request<YouTrackIssue>('POST', '/issues', issueData); return response.data; } async updateIssue(issueId: string, issueData: YouTrackUpdateIssueRequest): Promise<YouTrackIssue> { const response = await this.request<YouTrackIssue>('POST', `/issues/${issueId}`, issueData); return response.data; } async deleteIssue(issueId: string): Promise<void> { await this.request('DELETE', `/issues/${issueId}`); } async searchIssues(query: string, options?: YouTrackPaginationOptions): Promise<YouTrackIssue[]> { const params = { query, ...options }; const response = await this.request<YouTrackIssue[]>('GET', '/issues', undefined, params); return response.data; } // Issue Comments async getIssueComments(issueId: string, options?: YouTrackPaginationOptions): Promise<YouTrackIssueComment[]> { const response = await this.request<YouTrackIssueComment[]>( 'GET', `/issues/${issueId}/comments`, undefined, options ); return response.data; } async addComment(issueId: string, commentData: YouTrackCreateCommentRequest): Promise<YouTrackIssueComment> { const response = await this.request<YouTrackIssueComment>( 'POST', `/issues/${issueId}/comments`, commentData ); return response.data; } async updateComment( issueId: string, commentId: string, commentData: Partial<YouTrackCreateCommentRequest> ): Promise<YouTrackIssueComment> { const response = await this.request<YouTrackIssueComment>( 'POST', `/issues/${issueId}/comments/${commentId}`, commentData ); return response.data; } async deleteComment(issueId: string, commentId: string): Promise<void> { await this.request('DELETE', `/issues/${issueId}/comments/${commentId}`); } // Issue Attachments async getIssueAttachments(issueId: string, options?: YouTrackPaginationOptions): Promise<YouTrackIssueAttachment[]> { const response = await this.request<YouTrackIssueAttachment[]>( 'GET', `/issues/${issueId}/attachments`, undefined, options ); return response.data; } async addAttachment(issueId: string, file: Buffer, filename: string): Promise<YouTrackIssueAttachment> { const formData = new FormData(); formData.append('file', new Blob([file]), filename); const response = await this.client.post(`/issues/${issueId}/attachments`, formData, { headers: { 'Content-Type': 'multipart/form-data', }, }); return response.data; } // Work Items (Time Tracking) async getWorkItems(issueId: string, options?: YouTrackPaginationOptions): Promise<YouTrackWorkItem[]> { const response = await this.request<YouTrackWorkItem[]>( 'GET', `/issues/${issueId}/timeTracking/workItems`, undefined, options ); return response.data; } async addWorkItem(issueId: string, workItemData: YouTrackCreateWorkItemRequest): Promise<YouTrackWorkItem> { const response = await this.request<YouTrackWorkItem>( 'POST', `/issues/${issueId}/timeTracking/workItems`, workItemData ); return response.data; } async updateWorkItem( issueId: string, workItemId: string, workItemData: Partial<YouTrackCreateWorkItemRequest> ): Promise<YouTrackWorkItem> { const response = await this.request<YouTrackWorkItem>( 'POST', `/issues/${issueId}/timeTracking/workItems/${workItemId}`, workItemData ); return response.data; } async deleteWorkItem(issueId: string, workItemId: string): Promise<void> { await this.request('DELETE', `/issues/${issueId}/timeTracking/workItems/${workItemId}`); } // Agile Boards async listAgileBoards(options?: YouTrackPaginationOptions): Promise<YouTrackAgileBoard[]> { const response = await this.request<YouTrackAgileBoard[]>('GET', '/agiles', undefined, options); return response.data; } async getAgileBoard(boardId: string, fields?: string): Promise<YouTrackAgileBoard> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackAgileBoard>('GET', `/agiles/${boardId}`, undefined, params); return response.data; } async createAgileBoard(boardData: Partial<YouTrackAgileBoard>): Promise<YouTrackAgileBoard> { const response = await this.request<YouTrackAgileBoard>('POST', '/agiles', boardData); return response.data; } async updateAgileBoard(boardId: string, boardData: Partial<YouTrackAgileBoard>): Promise<YouTrackAgileBoard> { const response = await this.request<YouTrackAgileBoard>('POST', `/agiles/${boardId}`, boardData); return response.data; } // Sprints async listSprints(boardId: string, options?: YouTrackPaginationOptions): Promise<YouTrackSprint[]> { const response = await this.request<YouTrackSprint[]>( 'GET', `/agiles/${boardId}/sprints`, undefined, options ); return response.data; } async getSprint(boardId: string, sprintId: string, fields?: string): Promise<YouTrackSprint> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackSprint>( 'GET', `/agiles/${boardId}/sprints/${sprintId}`, undefined, params ); return response.data; } async createSprint(boardId: string, sprintData: Partial<YouTrackSprint>): Promise<YouTrackSprint> { const response = await this.request<YouTrackSprint>('POST', `/agiles/${boardId}/sprints`, sprintData); return response.data; } async updateSprint( boardId: string, sprintId: string, sprintData: Partial<YouTrackSprint> ): Promise<YouTrackSprint> { const response = await this.request<YouTrackSprint>( 'POST', `/agiles/${boardId}/sprints/${sprintId}`, sprintData ); return response.data; } // Tags async listTags(options?: YouTrackSearchOptions): Promise<YouTrackTag[]> { const response = await this.request<YouTrackTag[]>('GET', '/tags', undefined, options); return response.data; } async getTag(tagId: string, fields?: string): Promise<YouTrackTag> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackTag>('GET', `/tags/${tagId}`, undefined, params); return response.data; } // Custom Fields async listCustomFields(options?: YouTrackPaginationOptions): Promise<YouTrackCustomField[]> { const response = await this.request<YouTrackCustomField[]>('GET', '/admin/customFieldSettings/customFields', undefined, options); return response.data; } async getCustomField(fieldId: string, fields?: string): Promise<YouTrackCustomField> { const params = fields ? { fields } : undefined; const response = await this.request<YouTrackCustomField>( 'GET', `/admin/customFieldSettings/customFields/${fieldId}`, undefined, params ); return response.data; } // Issue Commands & Workflow async applyCommand(issueId: string, command: string, comment?: string): Promise<any> { const data = { query: command, ...(comment && { comment }) }; const response = await this.request('POST', `/issues/${issueId}/execute`, data); return response.data; } async getAvailableCommands(issueId: string): Promise<any[]> { const response = await this.request<any[]>('GET', `/issues/${issueId}/execute`); return response.data; } // Reports and Statistics async getProjectStatistics(projectId: string): Promise<any> { const response = await this.request('GET', `/admin/projects/${projectId}/statistics`); return response.data; } async generateReport(reportType: string, params?: any): Promise<any> { const response = await this.request('POST', `/reports/${reportType}`, params); return response.data; } // Utility Methods async ping(): Promise<boolean> { try { await this.request('GET', '/users/me'); return true; } catch { return false; } } async getServerInfo(): Promise<any> { const response = await this.request('GET', '/config'); return response.data; } }

Latest Blog Posts

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/lucyfuur94/youtrack-integration'

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