Skip to main content
Glama
server.ts31.9 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, McpError, ErrorCode, } from '@modelcontextprotocol/sdk/types.js'; import { YouTrackClient } from './client/youtrack.js'; import { YouTrackTools } from './tools/index.js'; import { YouTrackApiConfig } from './types/index.js'; import * as schemas from './tools/index.js'; export class YouTrackMCPServer { private server: Server; private client: YouTrackClient; private tools: YouTrackTools; constructor(config: YouTrackApiConfig) { this.server = new Server( { name: 'youtrack-mcp', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); this.client = new YouTrackClient(config); this.tools = new YouTrackTools(this.client); this.setupHandlers(); } private setupHandlers() { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ // Issue Management Tools { name: 'youtrack_list_issues', description: 'List issues with optional filtering and pagination', inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Filter by project short name or ID' }, query: { type: 'string', description: 'YouTrack search query string' }, assignee: { type: 'string', description: 'Filter by assignee login or ID' }, state: { type: 'string', description: 'Filter by issue state' }, priority: { type: 'string', description: 'Filter by priority' }, skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } } } }, { name: 'youtrack_get_issue', description: 'Get detailed information about a specific issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['issueId'] } }, { name: 'youtrack_create_issue', description: 'Create a new issue', inputSchema: { type: 'object', properties: { project: { type: 'string', description: 'Project short name or ID' }, summary: { type: 'string', description: 'Issue title/summary' }, description: { type: 'string', description: 'Issue description' }, assignee: { type: 'string', description: 'Assignee login or ID' }, priority: { type: 'string', description: 'Priority name or ID' }, type: { type: 'string', description: 'Issue type name or ID' }, tags: { type: 'array', items: { type: 'string' }, description: 'List of tag names or IDs' } }, required: ['project', 'summary'] } }, { name: 'youtrack_update_issue', description: 'Update an existing issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, summary: { type: 'string', description: 'Updated issue title/summary' }, description: { type: 'string', description: 'Updated issue description' }, assignee: { type: 'string', description: 'Updated assignee login or ID' }, priority: { type: 'string', description: 'Updated priority name or ID' }, state: { type: 'string', description: 'Updated state name or ID' }, tags: { type: 'array', items: { type: 'string' }, description: 'Updated list of tag names or IDs' } }, required: ['issueId'] } }, { name: 'youtrack_delete_issue', description: 'Delete an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' } }, required: ['issueId'] } }, { name: 'youtrack_search_issues', description: 'Search issues using YouTrack query syntax', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'YouTrack search query (e.g., "assignee: me state: Open")' }, skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['query'] } }, // Comment Management Tools { name: 'youtrack_get_comments', description: 'Get all comments for an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 } }, required: ['issueId'] } }, { name: 'youtrack_add_comment', description: 'Add a comment to an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, text: { type: 'string', description: 'Comment text' }, usesMarkdown: { type: 'boolean', description: 'Whether the comment uses Markdown formatting', default: false } }, required: ['issueId', 'text'] } }, { name: 'youtrack_update_comment', description: 'Update an existing comment', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, commentId: { type: 'string', description: 'Comment ID' }, text: { type: 'string', description: 'Updated comment text' }, usesMarkdown: { type: 'boolean', description: 'Whether the comment uses Markdown formatting', default: false } }, required: ['issueId', 'commentId', 'text'] } }, { name: 'youtrack_delete_comment', description: 'Delete a comment', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, commentId: { type: 'string', description: 'Comment ID' } }, required: ['issueId', 'commentId'] } }, // Attachment Management Tools { name: 'youtrack_get_attachments', description: 'Get all attachments for an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 } }, required: ['issueId'] } }, // Work Item Management Tools { name: 'youtrack_get_work_items', description: 'Get work items (time tracking entries) for an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 } }, required: ['issueId'] } }, { name: 'youtrack_add_work_item', description: 'Add a work item (time tracking entry) to an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, duration: { type: ['number', 'string'], description: 'Duration in minutes (number) or time string (e.g., "2h 30m")' }, description: { type: 'string', description: 'Work description' }, date: { type: 'number', description: 'Work date as timestamp (default: current time)' }, type: { type: 'string', description: 'Work item type name or ID' } }, required: ['issueId', 'duration'] } }, { name: 'youtrack_update_work_item', description: 'Update an existing work item', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, workItemId: { type: 'string', description: 'Work item ID' }, duration: { type: ['number', 'string'], description: 'Updated duration in minutes (number) or time string (e.g., "2h 30m")' }, description: { type: 'string', description: 'Updated work description' }, date: { type: 'number', description: 'Updated work date as timestamp' }, type: { type: 'string', description: 'Updated work item type name or ID' } }, required: ['issueId', 'workItemId'] } }, { name: 'youtrack_delete_work_item', description: 'Delete a work item', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, workItemId: { type: 'string', description: 'Work item ID' } }, required: ['issueId', 'workItemId'] } }, // Project Management Tools { name: 'youtrack_list_projects', description: 'List all accessible projects', inputSchema: { type: 'object', properties: { skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } } } }, { name: 'youtrack_get_project', description: 'Get detailed information about a specific project', inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: 'Project short name or ID' }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['projectId'] } }, { name: 'youtrack_create_project', description: 'Create a new project', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Project name' }, shortName: { type: 'string', description: 'Project short name (used as prefix for issues)' }, description: { type: 'string', description: 'Project description' }, leader: { type: 'string', description: 'Project leader login or ID' } }, required: ['name', 'shortName'] } }, { name: 'youtrack_update_project', description: 'Update an existing project', inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: 'Project short name or ID' }, name: { type: 'string', description: 'Updated project name' }, description: { type: 'string', description: 'Updated project description' }, leader: { type: 'string', description: 'Updated project leader login or ID' }, archived: { type: 'boolean', description: 'Whether the project should be archived' } }, required: ['projectId'] } }, { name: 'youtrack_delete_project', description: 'Delete a project', inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: 'Project short name or ID' } }, required: ['projectId'] } }, { name: 'youtrack_get_project_custom_fields', description: 'Get custom fields for a project', inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: 'Project short name or ID' }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['projectId'] } }, // User Management Tools { name: 'youtrack_get_current_user', description: 'Get current authenticated user information', inputSchema: { type: 'object', properties: { fields: { type: 'string', description: 'Comma-separated list of fields to return' } } } }, { name: 'youtrack_list_users', description: 'List users with optional search and pagination', inputSchema: { type: 'object', properties: { query: { type: 'string', description: 'Search query for user names or logins' }, skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } } } }, { name: 'youtrack_get_user', description: 'Get detailed information about a specific user', inputSchema: { type: 'object', properties: { userId: { type: 'string', description: 'User login or ID' }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['userId'] } }, { name: 'youtrack_list_groups', description: 'List user groups', inputSchema: { type: 'object', properties: { skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } } } }, { name: 'youtrack_get_group', description: 'Get detailed information about a specific group', inputSchema: { type: 'object', properties: { groupId: { type: 'string', description: 'Group name or ID' }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['groupId'] } }, // Workflow Management Tools { name: 'youtrack_get_issue_commands', description: 'Get available workflow commands for an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' } }, required: ['issueId'] } }, { name: 'youtrack_apply_workflow_command', description: 'Apply a workflow command to an issue', inputSchema: { type: 'object', properties: { issueId: { type: 'string', description: 'Issue ID (e.g., PROJECT-123)' }, command: { type: 'string', description: 'Workflow command (e.g., "assignee me", "state Fixed")' }, comment: { type: 'string', description: 'Optional comment to add with the command' } }, required: ['issueId', 'command'] } }, // Agile Board Management Tools { name: 'youtrack_list_agile_boards', description: 'List all agile boards', inputSchema: { type: 'object', properties: { skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } } } }, { name: 'youtrack_get_agile_board', description: 'Get detailed information about a specific agile board', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'Agile board ID' }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['boardId'] } }, { name: 'youtrack_create_agile_board', description: 'Create a new agile board', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'Board name' }, projects: { type: 'array', items: { type: 'string' }, description: 'List of project IDs to include in the board' } }, required: ['name', 'projects'] } }, { name: 'youtrack_update_agile_board', description: 'Update an existing agile board', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'Agile board ID' }, name: { type: 'string', description: 'Updated board name' }, projects: { type: 'array', items: { type: 'string' }, description: 'Updated list of project IDs' } }, required: ['boardId'] } }, // Sprint Management Tools { name: 'youtrack_list_sprints', description: 'List sprints for an agile board', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'Agile board ID' }, skip: { type: 'number', description: 'Number of items to skip for pagination', default: 0 }, top: { type: 'number', description: 'Maximum number of items to return', default: 50 }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['boardId'] } }, { name: 'youtrack_get_sprint', description: 'Get detailed information about a specific sprint', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'Agile board ID' }, sprintId: { type: 'string', description: 'Sprint ID' }, fields: { type: 'string', description: 'Comma-separated list of fields to return' } }, required: ['boardId', 'sprintId'] } }, { name: 'youtrack_create_sprint', description: 'Create a new sprint', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'Agile board ID' }, name: { type: 'string', description: 'Sprint name' }, goal: { type: 'string', description: 'Sprint goal' }, start: { type: 'number', description: 'Sprint start date as timestamp' }, finish: { type: 'number', description: 'Sprint end date as timestamp' } }, required: ['boardId', 'name'] } }, { name: 'youtrack_update_sprint', description: 'Update an existing sprint', inputSchema: { type: 'object', properties: { boardId: { type: 'string', description: 'Agile board ID' }, sprintId: { type: 'string', description: 'Sprint ID' }, name: { type: 'string', description: 'Updated sprint name' }, goal: { type: 'string', description: 'Updated sprint goal' }, start: { type: 'number', description: 'Updated sprint start date as timestamp' }, finish: { type: 'number', description: 'Updated sprint end date as timestamp' }, archived: { type: 'boolean', description: 'Whether the sprint should be archived' } }, required: ['boardId', 'sprintId'] } }, // Utility Tools { name: 'youtrack_ping', description: 'Test YouTrack connection', inputSchema: { type: 'object', properties: {} } }, { name: 'youtrack_get_server_info', description: 'Get YouTrack server information', inputSchema: { type: 'object', properties: {} } }, // Statistics and Reporting Tools { name: 'youtrack_get_project_statistics', description: 'Get project statistics and metrics', inputSchema: { type: 'object', properties: { projectId: { type: 'string', description: 'Project short name or ID' } }, required: ['projectId'] } }, { name: 'youtrack_generate_report', description: 'Generate custom reports', inputSchema: { type: 'object', properties: { reportType: { type: 'string', description: 'Type of report to generate' }, parameters: { type: 'object', description: 'Report-specific parameters' } }, required: ['reportType'] } } ], }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { let result: any; switch (name) { // Issue Management case 'youtrack_list_issues': result = await this.tools.listIssues(schemas.ListIssuesSchema.parse(args)); break; case 'youtrack_get_issue': result = await this.tools.getIssue(schemas.GetIssueSchema.parse(args)); break; case 'youtrack_create_issue': result = await this.tools.createIssue(schemas.CreateIssueSchema.parse(args)); break; case 'youtrack_update_issue': result = await this.tools.updateIssue(schemas.UpdateIssueSchema.parse(args)); break; case 'youtrack_delete_issue': result = await this.tools.deleteIssue(args as { issueId: string }); break; case 'youtrack_search_issues': result = await this.tools.searchIssues(schemas.SearchIssuesSchema.parse(args)); break; // Comment Management case 'youtrack_get_comments': result = await this.tools.getComments(args as { issueId: string; skip?: number; top?: number }); break; case 'youtrack_add_comment': result = await this.tools.addComment(schemas.AddCommentSchema.parse(args)); break; case 'youtrack_update_comment': result = await this.tools.updateComment(schemas.UpdateCommentSchema.parse(args)); break; case 'youtrack_delete_comment': result = await this.tools.deleteComment(schemas.DeleteCommentSchema.parse(args)); break; // Attachment Management case 'youtrack_get_attachments': result = await this.tools.getAttachments(args as { issueId: string; skip?: number; top?: number }); break; // Work Item Management case 'youtrack_get_work_items': result = await this.tools.getWorkItems(args as { issueId: string; skip?: number; top?: number }); break; case 'youtrack_add_work_item': result = await this.tools.addWorkItem(schemas.AddWorkItemSchema.parse(args)); break; case 'youtrack_update_work_item': result = await this.tools.updateWorkItem(schemas.UpdateWorkItemSchema.parse(args)); break; case 'youtrack_delete_work_item': result = await this.tools.deleteWorkItem(schemas.DeleteWorkItemSchema.parse(args)); break; // Project Management case 'youtrack_list_projects': result = await this.tools.listProjects(schemas.ListProjectsSchema.parse(args)); break; case 'youtrack_get_project': result = await this.tools.getProject(schemas.GetProjectSchema.parse(args)); break; case 'youtrack_create_project': result = await this.tools.createProject(schemas.CreateProjectSchema.parse(args)); break; case 'youtrack_update_project': result = await this.tools.updateProject(schemas.UpdateProjectSchema.parse(args)); break; case 'youtrack_delete_project': result = await this.tools.deleteProject(schemas.DeleteProjectSchema.parse(args)); break; case 'youtrack_get_project_custom_fields': result = await this.tools.getProjectCustomFields(args as { projectId: string; fields?: string }); break; // User Management case 'youtrack_get_current_user': result = await this.tools.getCurrentUser(args as { fields?: string }); break; case 'youtrack_list_users': result = await this.tools.listUsers(schemas.ListUsersSchema.parse(args)); break; case 'youtrack_get_user': result = await this.tools.getUser(schemas.GetUserSchema.parse(args)); break; case 'youtrack_list_groups': result = await this.tools.listGroups(schemas.ListGroupsSchema.parse(args)); break; case 'youtrack_get_group': result = await this.tools.getGroup(schemas.GetGroupSchema.parse(args)); break; // Workflow Management case 'youtrack_get_issue_commands': result = await this.tools.getIssueCommands(args as { issueId: string }); break; case 'youtrack_apply_workflow_command': result = await this.tools.applyWorkflowCommand(schemas.ApplyCommandSchema.parse(args)); break; // Agile Board Management case 'youtrack_list_agile_boards': result = await this.tools.listAgileBoards(schemas.ListAgileBoardsSchema.parse(args)); break; case 'youtrack_get_agile_board': result = await this.tools.getAgileBoard(schemas.GetAgileBoardSchema.parse(args)); break; case 'youtrack_create_agile_board': result = await this.tools.createAgileBoard(schemas.CreateAgileBoardSchema.parse(args)); break; case 'youtrack_update_agile_board': result = await this.tools.updateAgileBoard(args as { boardId: string; name?: string; projects?: string[] }); break; // Sprint Management case 'youtrack_list_sprints': result = await this.tools.listSprints(schemas.ListSprintsSchema.parse(args)); break; case 'youtrack_get_sprint': result = await this.tools.getSprint(args as { boardId: string; sprintId: string; fields?: string }); break; case 'youtrack_create_sprint': result = await this.tools.createSprint(args as { boardId: string; name: string; goal?: string; start?: number; finish?: number }); break; case 'youtrack_update_sprint': result = await this.tools.updateSprint(args as { boardId: string; sprintId: string; name?: string; goal?: string; start?: number; finish?: number; archived?: boolean }); break; // Utility Tools case 'youtrack_ping': result = await this.tools.ping(); break; case 'youtrack_get_server_info': result = await this.tools.getServerInfo(); break; // Statistics and Reporting case 'youtrack_get_project_statistics': result = await this.tools.getProjectStatistics(args as { projectId: string }); break; case 'youtrack_generate_report': result = await this.tools.generateReport(args as { reportType: string; parameters?: any }); break; default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error: any) { if (error instanceof McpError) { throw error; } throw new McpError( ErrorCode.InternalError, `Error executing tool ${name}: ${error.message}` ); } }); } async run() { const transport = new StdioServerTransport(); await this.server.connect(transport); console.error('YouTrack MCP server running on stdio'); } }

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