UNPKG

zephyr-scale-mcp-server

Version:

Model Context Protocol (MCP) server for Zephyr Scale test case management with comprehensive STEP_BY_STEP, PLAIN_TEXT, and BDD support

262 lines (239 loc) 9.19 kB
import { readFile } from 'fs/promises'; import { extname } from 'path'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { AxiosInstance } from 'axios'; let axiosInstance: AxiosInstance | null = null; export function setAxiosInstance(instance: AxiosInstance) { axiosInstance = instance; } export const resourceList = [ { uri: 'file://', name: 'File System Access', description: 'Read user-provided files containing test case examples, payloads, or templates. Use file:// followed by absolute path (e.g., file:///Users/username/examples/test-payload.json)', mimeType: 'application/octet-stream', }, { uri: 'zephyr://testcase/', name: 'Live Test Case Data', description: 'Fetch real test case data from Zephyr Scale to use as templates. Use zephyr://testcase/TEST-KEY (e.g., zephyr://testcase/PROJ-T123). The fetched data shows the exact structure including customFields, folder paths, and other project-specific configurations that you can copy when creating new test cases.', mimeType: 'application/json', }, { uri: 'zephyr://examples/step-by-step-payload', name: 'Step-by-Step Test Case - Request Payload Example', description: 'Example payload for creating a step-by-step test case', mimeType: 'application/json', }, { uri: 'zephyr://examples/gherkin-conversion', name: 'BDD Content Conversion Example', description: 'Shows how BDD content is converted from markdown to Gherkin format', mimeType: 'text/plain', }, ]; export async function readResource(uri: string) { // Handle file:// URIs for reading user-provided files if (uri.startsWith('file://')) { try { const filePath = uri.replace('file://', ''); const fileContent = await readFile(filePath, 'utf-8'); // Determine MIME type based on file extension const ext = extname(filePath).toLowerCase(); let mimeType = 'text/plain'; switch (ext) { case '.json': mimeType = 'application/json'; break; case '.yaml': case '.yml': mimeType = 'application/yaml'; break; case '.xml': mimeType = 'application/xml'; break; case '.html': mimeType = 'text/html'; break; case '.md': mimeType = 'text/markdown'; break; case '.js': mimeType = 'application/javascript'; break; case '.ts': mimeType = 'application/typescript'; break; default: mimeType = 'text/plain'; } return { contents: [{ uri: uri, mimeType: mimeType, text: fileContent }] }; } catch (error) { throw new McpError( ErrorCode.InternalError, `Failed to read file: ${error instanceof Error ? error.message : String(error)}` ); } } // Handle live test case data from Zephyr Scale if (uri.startsWith('zephyr://testcase/')) { if (!axiosInstance) { throw new McpError( ErrorCode.InternalError, 'Axios instance not initialized. Cannot fetch live test case data.' ); } try { const testCaseKey = uri.replace('zephyr://testcase/', ''); if (!testCaseKey) { throw new McpError( ErrorCode.InvalidRequest, 'Test case key is required. Use format: zephyr://testcase/TEST-KEY' ); } const response = await axiosInstance.get(`/rest/atm/1.0/testcase/${testCaseKey}`); return { contents: [{ uri: uri, mimeType: 'application/json', text: JSON.stringify({ description: `Live test case data for ${testCaseKey} retrieved from Zephyr Scale`, testCaseKey: testCaseKey, retrievedAt: new Date().toISOString(), data: response.data }, null, 2) }] }; } catch (error) { let errorMessage = 'Unknown error'; if (error instanceof Error && 'response' in error) { const axiosError = error as any; if (axiosError.response?.status === 404) { errorMessage = `Test case not found: ${uri.replace('zephyr://testcase/', '')}`; } else { errorMessage = `Status: ${axiosError.response?.status}, Data: ${JSON.stringify(axiosError.response?.data)}`; } } else if (error instanceof Error) { errorMessage = error.message; } else { errorMessage = String(error); } throw new McpError( ErrorCode.InternalError, `Failed to fetch test case: ${errorMessage}` ); } } switch (uri) { case 'zephyr://examples/bdd-test-case-payload': return { contents: [{ uri: uri, mimeType: 'application/json', text: JSON.stringify({ description: "Example payload sent to Zephyr Scale API for BDD test case creation", endpoint: "POST /rest/atm/1.0/testcase", payload: { projectKey: "PROJ", name: "User Authentication with Privacy Policy", status: "Draft", priority: "High", folder: "/ProjectName/Authentication/Login Features", precondition: "User has not accepted privacy policy and application is available", objective: "Verify that new users see and can accept privacy policy before proceeding", issueLinks: ["PROJ-123"], customFields: { "Type": "Functional", "Priority": "P0", "Regression": false, "Execution Type": "Manual", "Risk Control": false }, testScript: { type: "BDD", text: " Given I am a new user who has not accepted the privacy policy\\n And I navigate to the application\\n When I attempt to access the main features\\n Then I should see the Privacy Policy modal\\n And the modal should contain the privacy policy text\\n And the modal should have an 'Accept' button\\n And the modal should have a 'Decline' button\\n When I click the 'Accept' button\\n Then the Privacy Policy modal should close\\n And I should be able to access the application features\\n And my acceptance should be recorded in the system" } } }, null, 2) }] }; case 'zephyr://examples/step-by-step-payload': return { contents: [{ uri: uri, mimeType: 'application/json', text: JSON.stringify({ description: "Example payload for creating a step-by-step test case", endpoint: "POST /rest/atm/1.0/testcase", payload: { projectKey: "PROJ", name: "User Login Test", status: "Draft", priority: "High", folder: "/ProjectName/Authentication", customFields: { "Type": "Functional", "Priority": "P0" }, testScript: { type: "STEP_BY_STEP", steps: [ { description: "Navigate to login page", testData: "URL: https://example.com/login", expectedResult: "Login page is displayed with username and password fields" }, { description: "Enter valid credentials", testData: "Username: testuser@example.com, Password: validpassword123", expectedResult: "Credentials are entered successfully" }, { description: "Click login button", testData: "", expectedResult: "User is logged in and redirected to dashboard" } ] } } }, null, 2) }] }; case 'zephyr://examples/gherkin-conversion': return { contents: [{ uri: uri, mimeType: 'text/plain', text: `BDD Content Conversion Example The MCP server automatically converts markdown-style BDD content to proper Gherkin format. INPUT (Markdown style): **Given** a user with valid credentials **When** the user attempts to log in **Then** the user should be authenticated successfully OUTPUT (Gherkin format with indentation): Given a user with valid credentials When the user attempts to log in Then the user should be authenticated successfully SUPPORTED KEYWORDS: - **Given** → Given - **When** → When - **Then** → Then - **And** → And The converter: 1. Removes markdown formatting (**bold**) 2. Adds proper Gherkin keywords 3. Adds 4-space indentation to all lines 4. Filters out empty lines and separators (---) This ensures the BDD content is properly formatted for Zephyr Scale's BDD test script requirements.` }] }; default: throw new McpError(ErrorCode.InvalidRequest, `Unknown resource: ${uri}`); } }