Skip to main content
Glama

Xray MCP Server

index.ts24.6 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from '@modelcontextprotocol/sdk/types.js'; import { XrayClient, TestCase, TestExecution, TestRunStatus, TestPlan, TestSet } from './xray-client.js'; // Validate required environment variables const XRAY_CLIENT_ID = process.env.XRAY_CLIENT_ID; const XRAY_CLIENT_SECRET = process.env.XRAY_CLIENT_SECRET; if (!XRAY_CLIENT_ID || !XRAY_CLIENT_SECRET) { console.error('Error: XRAY_CLIENT_ID and XRAY_CLIENT_SECRET must be set in environment variables'); process.exit(1); } // Initialize Xray client const xrayClient = new XrayClient({ clientId: XRAY_CLIENT_ID, clientSecret: XRAY_CLIENT_SECRET, }); // Define available tools const tools: Tool[] = [ { name: 'create_test_case', description: 'Create a new test case in Xray Cloud', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, summary: { type: 'string', description: 'The test case summary/title', }, description: { type: 'string', description: 'The test case description', }, testType: { type: 'string', enum: ['Manual', 'Cucumber', 'Generic'], description: 'The type of test case', default: 'Manual', }, labels: { type: 'array', items: { type: 'string' }, description: 'Labels to attach to the test case', }, priority: { type: 'string', description: 'Priority of the test case (e.g., "High", "Medium", "Low")', }, }, required: ['projectKey', 'summary'], }, }, { name: 'get_test_case', description: 'Get details of a specific test case by key', inputSchema: { type: 'object', properties: { testKey: { type: 'string', description: 'The test case key (e.g., "PROJ-123")', }, }, required: ['testKey'], }, }, { name: 'update_test_case', description: 'Update an existing test case', inputSchema: { type: 'object', properties: { testKey: { type: 'string', description: 'The test case key (e.g., "PROJ-123")', }, summary: { type: 'string', description: 'New summary/title for the test case', }, description: { type: 'string', description: 'New description for the test case', }, labels: { type: 'array', items: { type: 'string' }, description: 'New labels for the test case', }, priority: { type: 'string', description: 'New priority for the test case', }, }, required: ['testKey'], }, }, { name: 'delete_test_case', description: 'Delete a test case', inputSchema: { type: 'object', properties: { testKey: { type: 'string', description: 'The test case key to delete (e.g., "PROJ-123")', }, }, required: ['testKey'], }, }, { name: 'search_test_cases', description: 'Search for test cases using JQL (Jira Query Language)', inputSchema: { type: 'object', properties: { jql: { type: 'string', description: 'JQL query to search test cases (e.g., "project = PROJ AND labels = automation")', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['jql'], }, }, { name: 'get_project_test_cases', description: 'Get all test cases for a specific project', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['projectKey'], }, }, // Test Execution tools { name: 'create_test_execution', description: 'Create a new test execution in Xray Cloud to run tests', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, summary: { type: 'string', description: 'The test execution summary/title', }, description: { type: 'string', description: 'The test execution description', }, testIssueIds: { type: 'array', items: { type: 'string' }, description: 'Array of test issue IDs to include in this execution (e.g., ["10001", "10002"])', }, testEnvironments: { type: 'array', items: { type: 'string' }, description: 'Array of test environments (e.g., ["Chrome", "iOS"])', }, }, required: ['projectKey', 'summary'], }, }, { name: 'get_test_execution', description: 'Get details of a specific test execution by key, including all test runs', inputSchema: { type: 'object', properties: { testExecutionKey: { type: 'string', description: 'The test execution key (e.g., "PROJ-456")', }, }, required: ['testExecutionKey'], }, }, { name: 'search_test_executions', description: 'Search for test executions using JQL (Jira Query Language)', inputSchema: { type: 'object', properties: { jql: { type: 'string', description: 'JQL query to search test executions (e.g., "project = PROJ AND created >= -7d")', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['jql'], }, }, { name: 'get_project_test_executions', description: 'Get all test executions for a specific project', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['projectKey'], }, }, { name: 'update_test_run_status', description: 'Update the status of a specific test run (e.g., mark as PASS or FAIL)', inputSchema: { type: 'object', properties: { testRunId: { type: 'string', description: 'The test run ID (obtained from test execution details)', }, status: { type: 'string', enum: ['TODO', 'EXECUTING', 'PASS', 'FAIL', 'ABORTED', 'PASSED', 'FAILED'], description: 'The new status for the test run', }, }, required: ['testRunId', 'status'], }, }, // Test Plan tools { name: 'create_test_plan', description: 'Create a new test plan in Xray Cloud to organize tests', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, summary: { type: 'string', description: 'The test plan summary/title', }, description: { type: 'string', description: 'The test plan description', }, testIssueIds: { type: 'array', items: { type: 'string' }, description: 'Array of test issue IDs to include in this plan', }, }, required: ['projectKey', 'summary'], }, }, { name: 'get_test_plan', description: 'Get details of a specific test plan by key, including all tests', inputSchema: { type: 'object', properties: { testPlanKey: { type: 'string', description: 'The test plan key (e.g., "PROJ-789")', }, }, required: ['testPlanKey'], }, }, { name: 'search_test_plans', description: 'Search for test plans using JQL (Jira Query Language)', inputSchema: { type: 'object', properties: { jql: { type: 'string', description: 'JQL query to search test plans', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['jql'], }, }, { name: 'get_project_test_plans', description: 'Get all test plans for a specific project', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['projectKey'], }, }, { name: 'add_tests_to_test_plan', description: 'Add tests to an existing test plan', inputSchema: { type: 'object', properties: { testPlanIssueId: { type: 'string', description: 'The test plan issue ID (not key)', }, testIssueIds: { type: 'array', items: { type: 'string' }, description: 'Array of test issue IDs to add', }, }, required: ['testPlanIssueId', 'testIssueIds'], }, }, { name: 'remove_tests_from_test_plan', description: 'Remove tests from an existing test plan', inputSchema: { type: 'object', properties: { testPlanIssueId: { type: 'string', description: 'The test plan issue ID (not key)', }, testIssueIds: { type: 'array', items: { type: 'string' }, description: 'Array of test issue IDs to remove', }, }, required: ['testPlanIssueId', 'testIssueIds'], }, }, // Test Set tools { name: 'create_test_set', description: 'Create a new test set in Xray Cloud to group related tests', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, summary: { type: 'string', description: 'The test set summary/title', }, description: { type: 'string', description: 'The test set description', }, testIssueIds: { type: 'array', items: { type: 'string' }, description: 'Array of test issue IDs to include in this set', }, }, required: ['projectKey', 'summary'], }, }, { name: 'get_test_set', description: 'Get details of a specific test set by key, including all tests', inputSchema: { type: 'object', properties: { testSetKey: { type: 'string', description: 'The test set key (e.g., "PROJ-890")', }, }, required: ['testSetKey'], }, }, { name: 'search_test_sets', description: 'Search for test sets using JQL (Jira Query Language)', inputSchema: { type: 'object', properties: { jql: { type: 'string', description: 'JQL query to search test sets', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['jql'], }, }, { name: 'get_project_test_sets', description: 'Get all test sets for a specific project', inputSchema: { type: 'object', properties: { projectKey: { type: 'string', description: 'The Jira project key (e.g., "PROJ")', }, maxResults: { type: 'number', description: 'Maximum number of results to return', default: 50, }, }, required: ['projectKey'], }, }, { name: 'add_tests_to_test_set', description: 'Add tests to an existing test set', inputSchema: { type: 'object', properties: { testSetIssueId: { type: 'string', description: 'The test set issue ID (not key)', }, testIssueIds: { type: 'array', items: { type: 'string' }, description: 'Array of test issue IDs to add', }, }, required: ['testSetIssueId', 'testIssueIds'], }, }, { name: 'remove_tests_from_test_set', description: 'Remove tests from an existing test set', inputSchema: { type: 'object', properties: { testSetIssueId: { type: 'string', description: 'The test set issue ID (not key)', }, testIssueIds: { type: 'array', items: { type: 'string' }, description: 'Array of test issue IDs to remove', }, }, required: ['testSetIssueId', 'testIssueIds'], }, }, ]; // Create server instance const server = new Server( { name: 'xray-mcp-server', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Handle tool listing server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // Handle tool execution server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (!args) { throw new Error('Missing arguments'); } try { switch (name) { case 'create_test_case': { const testCase: TestCase = { projectKey: args.projectKey as string, summary: args.summary as string, description: args.description as string | undefined, testType: args.testType as 'Manual' | 'Cucumber' | 'Generic' | undefined, labels: args.labels as string[] | undefined, priority: args.priority as string | undefined, }; const result = await xrayClient.createTestCase(testCase); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_test_case': { const result = await xrayClient.getTestCase(args.testKey as string); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'update_test_case': { const updates: Partial<TestCase> = {}; if (args.summary) updates.summary = args.summary as string; if (args.description) updates.description = args.description as string; if (args.labels) updates.labels = args.labels as string[]; if (args.priority) updates.priority = args.priority as string; await xrayClient.updateTestCase(args.testKey as string, updates); return { content: [ { type: 'text', text: `Test case ${args.testKey} updated successfully`, }, ], }; } case 'delete_test_case': { await xrayClient.deleteTestCase(args.testKey as string); return { content: [ { type: 'text', text: `Test case ${args.testKey} deleted successfully`, }, ], }; } case 'search_test_cases': { const result = await xrayClient.searchTestCases( args.jql as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_project_test_cases': { const result = await xrayClient.getTestCasesByProject( args.projectKey as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } // Test Execution handlers case 'create_test_execution': { const testExecution: TestExecution = { projectKey: args.projectKey as string, summary: args.summary as string, description: args.description as string | undefined, testIssueIds: args.testIssueIds as string[] | undefined, testEnvironments: args.testEnvironments as string[] | undefined, }; const result = await xrayClient.createTestExecution(testExecution); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_test_execution': { const result = await xrayClient.getTestExecution(args.testExecutionKey as string); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_test_executions': { const result = await xrayClient.searchTestExecutions( args.jql as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_project_test_executions': { const result = await xrayClient.getTestExecutionsByProject( args.projectKey as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'update_test_run_status': { const result = await xrayClient.updateTestRunStatus( args.testRunId as string, args.status as TestRunStatus ); return { content: [ { type: 'text', text: `Test run ${args.testRunId} status updated to ${args.status}: ${result}`, }, ], }; } case 'create_test_plan': { const testPlan: TestPlan = { projectKey: args.projectKey as string, summary: args.summary as string, description: args.description as string | undefined, testIssueIds: args.testIssueIds as string[] | undefined, }; const result = await xrayClient.createTestPlan(testPlan); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_test_plan': { const result = await xrayClient.getTestPlan(args.testPlanKey as string); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_test_plans': { const result = await xrayClient.searchTestPlans( args.jql as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_project_test_plans': { const result = await xrayClient.getTestPlansByProject( args.projectKey as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'add_tests_to_test_plan': { const result = await xrayClient.addTestsToTestPlan( args.testPlanIssueId as string, args.testIssueIds as string[] ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'remove_tests_from_test_plan': { const result = await xrayClient.removeTestsFromTestPlan( args.testPlanIssueId as string, args.testIssueIds as string[] ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'create_test_set': { const testSet: TestSet = { projectKey: args.projectKey as string, summary: args.summary as string, description: args.description as string | undefined, testIssueIds: args.testIssueIds as string[] | undefined, }; const result = await xrayClient.createTestSet(testSet); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_test_set': { const result = await xrayClient.getTestSet(args.testSetKey as string); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'search_test_sets': { const result = await xrayClient.searchTestSets( args.jql as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'get_project_test_sets': { const result = await xrayClient.getTestSetsByProject( args.projectKey as string, args.maxResults as number | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'add_tests_to_test_set': { const result = await xrayClient.addTestsToTestSet( args.testSetIssueId as string, args.testIssueIds as string[] ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } case 'remove_tests_from_test_set': { const result = await xrayClient.removeTestsFromTestSet( args.testSetIssueId as string, args.testIssueIds as string[] ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: 'text', text: `Error: ${errorMessage}`, }, ], isError: true, }; } }); // Start the server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); // Handle EPIPE errors (broken pipe when client disconnects) process.stdout.on('error', (err: NodeJS.ErrnoException) => { if (err.code === 'EPIPE') { process.exit(0); } }); process.stderr.on('error', (err: NodeJS.ErrnoException) => { if (err.code === 'EPIPE') { process.exit(0); } }); // Handle graceful shutdown process.on('SIGINT', async () => { await server.close(); process.exit(0); }); process.on('SIGTERM', async () => { await server.close(); process.exit(0); }); } main().catch((error) => { console.error('Fatal error in MCP server:', error); process.exit(1); });

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/c4m3lblue-star/xray-mcp'

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