Skip to main content
Glama
index-enhanced.tsโ€ข10.8 kB
import "dotenv/config"; import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // Import enhanced types and classes import { ZebrunnerApiClient } from "./api/client.js"; import { ZebrunnerToolHandlers } from "./handlers/tools.js"; import { ZebrunnerConfig, GetTestCasesInputSchema, GetTestSuitesInputSchema, GetTestRunsInputSchema, GetTestResultsInputSchema, FindTestCaseByKeyInputSchema, GetSuiteHierarchyInputSchema } from "./types/api.js"; // Legacy imports for backward compatibility import { ListTestSuitesSchema, GetTestSuiteSchema, ListTestCasesSchema, GetTestCaseSchema, GetTestCaseByKeySchema, SearchTestCasesSchema, TestSuiteSchema, TestCaseLiteSchema, TestCaseDetailsSchema } from "./types.js"; import { ZebrunnerClient } from "./zebrunnerClient.js"; import { FormatProcessor } from "./utils/formatter.js"; /** Environment configuration */ const ZEBRUNNER_URL = process.env.ZEBRUNNER_URL?.replace(/\/+$/, ""); const ZEBRUNNER_LOGIN = process.env.ZEBRUNNER_LOGIN; const ZEBRUNNER_TOKEN = process.env.ZEBRUNNER_TOKEN; const DEBUG_MODE = process.env.DEBUG === 'true'; if (!ZEBRUNNER_URL || !ZEBRUNNER_LOGIN || !ZEBRUNNER_TOKEN) { console.error("Missing required environment variables:"); console.error("- ZEBRUNNER_URL"); console.error("- ZEBRUNNER_LOGIN"); console.error("- ZEBRUNNER_TOKEN"); process.exit(1); } // Enhanced API client configuration const config: ZebrunnerConfig = { baseUrl: ZEBRUNNER_URL, username: ZEBRUNNER_LOGIN, token: ZEBRUNNER_TOKEN, timeout: 30_000, retryAttempts: 3, retryDelay: 1000, debug: DEBUG_MODE, defaultPageSize: 50, maxPageSize: 200 }; // Initialize clients const enhancedClient = new ZebrunnerApiClient(config); const legacyClient = new ZebrunnerClient({ baseUrl: ZEBRUNNER_URL, username: ZEBRUNNER_LOGIN, token: ZEBRUNNER_TOKEN }); const toolHandlers = new ZebrunnerToolHandlers(enhancedClient); /** Legacy markdown helper for backward compatibility */ function renderTestCaseMarkdown(tcRaw: any): string { return FormatProcessor.formatTestCaseMarkdown(tcRaw); } async function main() { const server = new McpServer( { name: "zebrunner-mcp-enhanced", version: "2.0.0" }, { capabilities: { tools: {} } } ); // ========== ENHANCED TOOLS (Based on Analysis Document) ========== // Test Cases Tools server.tool( "get_test_cases", "Retrieve test cases from Zebrunner with advanced filtering and pagination", { projectKey: z.string().min(1), suiteId: z.number().int().positive().optional(), rootSuiteId: z.number().int().positive().optional(), includeSteps: z.boolean().default(false), format: z.enum(['dto', 'json', 'string']).default('json'), page: z.number().int().nonnegative().optional(), size: z.number().int().positive().max(200).optional() }, async (args) => toolHandlers.getTestCases(args) ); server.tool( "find_test_case_by_key", "Find a specific test case by its key with detailed information", { projectKey: z.string().min(1), caseKey: z.string().min(1), includeSteps: z.boolean().default(true), format: z.enum(['dto', 'json', 'string']).default('json') }, async (args) => toolHandlers.findTestCaseByKey(args) ); // Test Suites Tools server.tool( "get_test_suites", "Retrieve test suites with hierarchy support and filtering", { projectKey: z.string().min(1), parentSuiteId: z.number().int().positive().optional(), rootOnly: z.boolean().default(false), includeHierarchy: z.boolean().default(false), format: z.enum(['dto', 'json', 'string']).default('json'), page: z.number().int().nonnegative().optional(), size: z.number().int().positive().max(200).optional() }, async (args) => toolHandlers.getTestSuites(args) ); server.tool( "get_suite_hierarchy", "Get hierarchical test suite tree with configurable depth", { projectKey: z.string().min(1), rootSuiteId: z.number().int().positive().optional(), maxDepth: z.number().int().positive().max(10).default(5), format: z.enum(['dto', 'json', 'string']).default('json') }, async (args) => toolHandlers.getSuiteHierarchy(args) ); server.tool( "get_root_suites", "Get only root-level test suites with hierarchy information", { projectKey: z.string().min(1), format: z.enum(['dto', 'json', 'string']).default('json') }, async (args) => toolHandlers.getRootSuites(args.projectKey, args.format) ); // Test Runs and Results Tools server.tool( "get_test_runs", "Retrieve test execution runs with filtering by status, milestone, build, etc.", { projectKey: z.string().min(1), status: z.string().optional(), milestone: z.string().optional(), build: z.string().optional(), environment: z.string().optional(), format: z.enum(['dto', 'json', 'string']).default('json'), page: z.number().int().nonnegative().optional(), size: z.number().int().positive().max(200).optional() }, async (args) => toolHandlers.getTestRuns(args) ); server.tool( "get_test_results", "Get test results for a specific test run", { projectKey: z.string().min(1), runId: z.number().int().positive(), status: z.string().optional(), format: z.enum(['dto', 'json', 'string']).default('json') }, async (args) => toolHandlers.getTestResults(args) ); // Utility Tools server.tool( "get_test_cases_by_suite", "Get all test cases belonging to a specific test suite", { projectKey: z.string().min(1), suiteId: z.number().int().positive(), format: z.enum(['dto', 'json', 'string']).default('json') }, async (args) => toolHandlers.getTestCasesBySuite(args.projectKey, args.suiteId, args.format) ); // ========== LEGACY TOOLS (Backward Compatibility) ========== server.tool( "list_test_suites", "Legacy: Return list of Zebrunner test suites for a project", { project_key: z.string().optional(), project_id: z.number().int().positive().optional() }, async (args) => { const { project_key, project_id } = args; if (!project_key && !project_id) { throw new Error("Either project_key or project_id must be provided"); } try { const suites = await legacyClient.listTestSuites({ projectKey: project_key, projectId: project_id }); const data = suites.map((s: unknown) => { const parsed = TestSuiteSchema.safeParse(s); return parsed.success ? parsed.data : s; }); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}` }] }; } } ); server.tool( "get_test_suite", "Legacy: Return detailed info of a test suite by suite_id (Limited availability)", { suite_id: z.number().int().positive() }, async (args) => { const { suite_id } = args; try { const suite = await legacyClient.getTestSuite(suite_id); const parsed = TestSuiteSchema.safeParse(suite); const data = parsed.success ? parsed.data : suite; return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}. Individual test suite details may not be available via API.` }] }; } } ); server.tool( "list_test_cases", "Legacy: Return list of test cases for a given test suite (Limited availability)", { suite_id: z.number().int().positive() }, async (args) => { const { suite_id } = args; try { const cases = await legacyClient.listTestCases(suite_id); const data = cases.map((c: unknown) => { const parsed = TestCaseLiteSchema.safeParse(c); return parsed.success ? parsed.data : c; }); return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}. Test cases for this suite may not be available via this endpoint.` }] }; } } ); server.tool( "get_test_case", "Legacy: Return detailed info of a test case by case_id", { case_id: z.number().int().positive() }, async (args) => { const { case_id } = args; try { const tc = await legacyClient.getTestCase(case_id); const parsed = TestCaseDetailsSchema.safeParse(tc); const data = parsed.success ? parsed.data : tc; const md = renderTestCaseMarkdown(tc); return { content: [ { type: "text", text: `**JSON Data:**\n\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\`` }, { type: "text", text: `**Markdown Export:**\n\n${md}` } ] }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}` }] }; } } ); server.tool( "get_test_case_by_key", "Legacy: Return detailed info of a test case by case_key and project_key (โœ… Working)", { case_key: z.string().min(1), project_key: z.string().min(1) }, async (args) => { const { case_key, project_key } = args; try { const tc = await legacyClient.getTestCaseByKey(case_key, project_key); const parsed = TestCaseDetailsSchema.safeParse(tc); const data = parsed.success ? parsed.data : tc; const md = renderTestCaseMarkdown(tc); return { content: [ { type: "text", text: `**JSON Data:**\n\n\`\`\`json\n${JSON.stringify(data, null, 2)}\n\`\`\`` }, { type: "text", text: `**Markdown Export:**\n\n${md}` } ] }; } catch (error: any) { return { content: [{ type: "text", text: `Error: ${error.message}` }] }; } } ); // Start the server const transport = new StdioServerTransport(); await server.connect(transport); if (DEBUG_MODE) { console.log("Zebrunner MCP Server Enhanced started in debug mode"); } } main().catch((e) => { console.error("MCP server failed to start:", e); process.exit(1); });

Implementation Reference

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/maksimsarychau/mcp-zebrunner'

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