# Directory Structure ``` ├── .eslintrc.json ├── .github │ └── workflows │ └── test.yml ├── .gitignore ├── .node-version ├── .prettierrc ├── Dockerfile ├── eslint.config.js ├── LICENSE ├── package-lock.json ├── package.json ├── README.md ├── src │ ├── index.ts │ ├── operations │ │ ├── cases.ts │ │ ├── plans.ts │ │ ├── projects.ts │ │ ├── results.ts │ │ ├── runs.ts │ │ ├── shared-steps.ts │ │ └── suites.ts │ └── utils.ts └── tsconfig.json ``` # Files -------------------------------------------------------------------------------- /.node-version: -------------------------------------------------------------------------------- ``` 22.13.1 ``` -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- ``` node_modules/ build/ *.log .env* ``` -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- ``` { "singleQuote": true, "trailingComma": "all", "printWidth": 80 } ``` -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- ```json { "env": { "browser": true, "es2021": true }, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier" ], "parser": "@typescript-eslint/parser", "parserOptions": { "ecmaVersion": 12, "sourceType": "module" }, "plugins": [ "@typescript-eslint", "prettier" ], "rules": { "prettier/prettier": "error" } } ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- ```markdown # QASE MCP Server MCP server implementation for Qase API This is a TypeScript-based MCP server that provides integration with the Qase test management platform. It implements core MCP concepts by providing tools for interacting with various Qase entities. ## Features ### Tools The server provides tools for interacting with the Qase API, allowing you to manage the following entities: #### Projects - `list_projects` - Get all projects - `get_project` - Get project by code - `create_project` - Create new project - `delete_project` - Delete project by code #### Test Cases - `get_cases` - Get all test cases in a project - `get_case` - Get a specific test case - `create_case` - Create a new test case - `update_case` - Update an existing test case #### Test Runs - `get_runs` - Get all test runs in a project - `get_run` - Get a specific test run #### Test Results - `get_results` - Get all test run results for a project - `get_result` - Get test run result by code and hash - `create_result` - Create test run result - `create_result_bulk` - Create multiple test run results in bulk - `update_result` - Update an existing test run result #### Test Plans - `get_plans` - Get all test plans in a project - `get_plan` - Get a specific test plan - `create_plan` - Create a new test plan - `update_plan` - Update an existing test plan - `delete_plan` - Delete a test plan #### Test Suites - `get_suites` - Get all test suites in a project - `get_suite` - Get a specific test suite - `create_suite` - Create a new test suite - `update_suite` - Update an existing test suite - `delete_suite` - Delete a test suite #### Shared Steps - `get_shared_steps` - Get all shared steps in a project - `get_shared_step` - Get a specific shared step - `create_shared_step` - Create a new shared step - `update_shared_step` - Update an existing shared step - `delete_shared_step` - Delete a shared step ## Development Install dependencies: ```bash npm install ``` Build the server: ```bash npm run build ``` For development with auto-rebuild: ```bash npm run watch ``` ## Installation ### Claude Desktop To use with Claude Desktop, add the server config: - On MacOS: `~/Library/Application Support/Claude/claude_desktop_config.json` - On Windows: `%APPDATA%/Claude/claude_desktop_config.json` ```json { "mcpServers": { "mcp-qase": { "command": "/path/to/mcp-qase/build/index.js", "env": { "QASE_API_TOKEN": "<YOUR_TOKEN>" } } } } ``` ### Cursor To use with Cursor, register the command as follows: ``` env QASE_API_TOKEN=<YOUR_TOKEN> /path/to/mcp-qase/build/index.js ``` ## Debugging Since MCP servers communicate over stdio, debugging can be challenging. We recommend using the [MCP Inspector](https://github.com/modelcontextprotocol/inspector): ```bash npx -y @modelcontextprotocol/inspector -e QASE_API_TOKEN=<YOUR_TOKEN> ./build/index.js ``` ``` -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- ```json { "compilerOptions": { "target": "ES2022", "module": "Node16", "moduleResolution": "Node16", "outDir": "./build", "rootDir": "./src", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules"] } ``` -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- ```yaml name: Test on: [push, pull_request] jobs: lint: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v2 - name: Set up Node.js uses: actions/setup-node@v4 with: node-version: 22 - name: Install dependencies run: npm install - name: Run ESLint run: npm run lint ``` -------------------------------------------------------------------------------- /src/operations/runs.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { toResult } from '../utils.js'; import { apply, pipe } from 'ramda'; import { client } from '../utils.js'; export const GetRunsSchema = z.object({ code: z.string(), search: z.string().optional(), status: z.string().optional(), milestone: z.number().optional(), environment: z.number().optional(), fromStartTime: z.number().optional(), toStartTime: z.number().optional(), limit: z.number().optional(), offset: z.number().optional(), include: z.string().optional(), }); export const GetRunSchema = z.object({ code: z.string(), id: z.number(), include: z.enum(['cases']).optional(), }); export const getRuns = pipe( apply(client.runs.getRuns.bind(client.runs)), toResult, ); export const getRun = pipe(client.runs.getRun.bind(client.runs), toResult); ``` -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- ```javascript export default { languageOptions: { ecmaVersion: 12, sourceType: "module", globals: { browser: true, es2021: true, }, }, files: ["**/*.ts", "**/*.tsx"], plugins: { "@typescript-eslint": (await import("@typescript-eslint/eslint-plugin")).default, "prettier": (await import("eslint-plugin-prettier")).default, }, rules: { ...(await import("eslint-config-prettier")).default.rules, ...(await import("eslint-plugin-prettier")).default.configs.recommended.rules, ...(await import("@typescript-eslint/eslint-plugin")).default.configs.recommended.rules, }, languageOptions: { parser: (await import("@typescript-eslint/parser")).default, ecmaVersion: 12, sourceType: "module", globals: { browser: true, es2021: true, }, }, }; ``` -------------------------------------------------------------------------------- /src/operations/projects.ts: -------------------------------------------------------------------------------- ```typescript import { ProjectCreateAccessEnum } from 'qaseio'; import { z } from 'zod'; import { client, toResult } from '../utils.js'; import { pipe } from 'ramda'; export const ListProjectsSchema = z.object({ limit: z.number().optional(), offset: z.number().optional(), }); export const GetProjectSchema = z.object({ code: z.string(), }); export const CreateProjectSchema = z.object({ code: z.string(), title: z.string(), description: z.string().optional(), access: z.nativeEnum(ProjectCreateAccessEnum).optional(), group: z.string().optional(), }); export const listProjects = pipe( client.projects.getProjects.bind(client.projects), toResult, ); export const getProject = pipe( client.projects.getProject.bind(client.projects), toResult, ); export const createProject = pipe( client.projects.createProject.bind(client.projects), toResult, ); ``` -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- ```typescript import { AxiosResponse } from 'axios'; import { ResultAsync } from 'neverthrow'; import { QaseApi } from 'qaseio'; export type ApiError = { response?: { data?: { message?: string } }; message: string; }; export type ApiResponse<T> = { data: { result?: T; status: boolean; errorMessage?: string; }; }; export const formatApiError = (error: unknown) => { const apiError = error as ApiError; return apiError.response?.data?.message || apiError.message; }; export const toResult = (promise: Promise<AxiosResponse>) => ResultAsync.fromPromise(promise, formatApiError); export const client = (({ QASE_API_TOKEN }) => { if (!QASE_API_TOKEN) { throw new Error( 'QASE_API_TOKEN environment variable is required. Please set it before running the server.', ); } return new QaseApi({ token: QASE_API_TOKEN, }); })(process.env); ``` -------------------------------------------------------------------------------- /src/operations/plans.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { client, toResult } from '../utils.js'; import { pipe } from 'ramda'; export const GetPlansSchema = z.object({ code: z.string(), limit: z.number().optional(), offset: z.number().optional(), }); export const GetPlanSchema = z.object({ code: z.string(), id: z.number(), }); export const CreatePlanSchema = z.object({ code: z.string(), title: z.string(), description: z.string().optional(), cases: z.array(z.number()), }); export const UpdatePlanSchema = z.object({ code: z.string(), id: z.number(), title: z.string().optional(), description: z.string().optional(), cases: z.array(z.number()).optional(), }); export const getPlans = pipe( client.plans.getPlans.bind(client.plans), toResult, ); export const getPlan = pipe(client.plans.getPlan.bind(client.plans), toResult); export const createPlan = pipe( client.plans.createPlan.bind(client.plans), toResult, ); export const updatePlan = pipe( client.plans.updatePlan.bind(client.plans), toResult, ); ``` -------------------------------------------------------------------------------- /src/operations/suites.ts: -------------------------------------------------------------------------------- ```typescript import { z } from 'zod'; import { client, toResult } from '../utils.js'; import { pipe } from 'ramda'; export const GetSuitesSchema = z.object({ code: z.string(), search: z.string().optional(), limit: z.number().optional(), offset: z.number().optional(), }); export const GetSuiteSchema = z.object({ code: z.string(), id: z.number(), }); export const CreateSuiteSchema = z.object({ code: z.string(), title: z.string(), description: z.string().optional(), preconditions: z.string().optional(), parent_id: z.number().optional(), }); export const UpdateSuiteSchema = z.object({ code: z.string(), id: z.number(), title: z.string().optional(), description: z.string().optional(), preconditions: z.string().optional(), parent_id: z.number().optional(), }); export const getSuites = pipe( client.suites.getSuites.bind(client.suites), toResult, ); export const getSuite = pipe( client.suites.getSuite.bind(client.suites), toResult, ); export const createSuite = pipe( client.suites.createSuite.bind(client.suites), toResult, ); export const updateSuite = pipe( client.suites.updateSuite.bind(client.suites), toResult, ); ``` -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- ```json { "name": "mcp-qase", "version": "0.1.0", "description": "MCP server implementation for Qase API", "private": true, "type": "module", "bin": { "mcp-qase": "./build/index.js" }, "files": [ "build" ], "scripts": { "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"", "prepare": "npm run build", "watch": "tsc --watch", "inspector": "npx @modelcontextprotocol/inspector build/index.js", "lint": "eslint src --ext .ts", "lint:fix": "eslint . --ext .ts --fix" }, "dependencies": { "@modelcontextprotocol/sdk": "0.6.0", "@types/ramda": "^0.30.2", "neverthrow": "^8.2.0", "qaseio": "^2.4.1", "ramda": "^0.30.1", "ts-pattern": "^5.6.2", "zod": "^3.24.2", "zod-to-json-schema": "^3.24.3" }, "devDependencies": { "@types/node": "^20.11.24", "@typescript-eslint/eslint-plugin": "^8.26.1", "eslint": "^9.22.0", "eslint-config-prettier": "^10.1.1", "eslint-plugin-prettier": "^5.2.3", "lint-staged": "^12.0.0", "typescript": "^5.3.3" }, "lint-staged": { "*.{js,ts,tsx}": [ "eslint --fix", "prettier --write" ] } } ``` -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- ```dockerfile FROM debian:bullseye-slim ENV DEBIAN_FRONTEND=noninteractive \ GLAMA_VERSION="0.2.0" \ PATH="/home/service-user/.local/bin:${PATH}" RUN (groupadd -r service-user) && (useradd -u 1987 -r -m -g service-user service-user) && (mkdir -p /home/service-user/.local/bin /app) && (chown -R service-user:service-user /home/service-user /app) && (apt-get update) && (apt-get install -y --no-install-recommends build-essential curl wget software-properties-common libssl-dev zlib1g-dev git) && (rm -rf /var/lib/apt/lists/*) && (curl -fsSL https://deb.nodesource.com/setup_22.x | bash -) && (apt-get install -y nodejs) && (apt-get clean) && (npm install -g [email protected]) && (npm install -g [email protected]) && (npm install -g [email protected]) && (node --version) && (curl -LsSf https://astral.sh/uv/install.sh | UV_INSTALL_DIR="/usr/local/bin" sh) && (uv python install 3.13 --default --preview) && (ln -s $(uv python find) /usr/local/bin/python) && (python --version) && (apt-get clean) && (rm -rf /var/lib/apt/lists/*) && (rm -rf /tmp/*) && (rm -rf /var/tmp/*) && (su - service-user -c "uv python install 3.13 --default --preview && python --version") USER service-user WORKDIR /app RUN git clone https://github.com/rikuson/mcp-qase . RUN (npm install) && (npm run build) CMD ["mcp-proxy","node","./build/index.js"] ``` -------------------------------------------------------------------------------- /src/operations/results.ts: -------------------------------------------------------------------------------- ```typescript import { TestStepResultCreateStatusEnum, ResultCreate, ResultCreateBulk, ResultUpdate, } from 'qaseio'; import { z } from 'zod'; import { toResult } from '../utils.js'; import { apply, pipe } from 'ramda'; import { client } from '../utils.js'; export const GetResultsSchema = z.object({ code: z.string(), limit: z.string().optional(), offset: z.string().optional(), status: z.nativeEnum(TestStepResultCreateStatusEnum).optional(), from: z.string().optional(), to: z.string().optional(), }); export const GetResultSchema = z.object({ code: z.string(), hash: z.string(), }); export const CreateResultSchema = z.object({ code: z.string(), id: z.number(), result: z.record(z.any()).transform((v) => v as ResultCreate), }); export const CreateResultBulkSchema = z.object({ code: z.string(), id: z.number(), results: z.record(z.any()).transform((v) => v as ResultCreateBulk), }); export const UpdateResultSchema = z.object({ code: z.string(), id: z.number(), hash: z.string(), result: z.record(z.any()).transform((v) => v as ResultUpdate), }); export const getResults = pipe( apply(client.results.getResults.bind(client.results)), toResult, ); export const getResult = pipe( client.results.getResult.bind(client.results), toResult, ); export const createResult = pipe( client.results.createResult.bind(client.results), toResult, ); export const createResultBulk = pipe( client.results.createResultBulk.bind(client.results), toResult, ); export const updateResult = pipe( client.results.updateResult.bind(client.results), toResult, ); ``` -------------------------------------------------------------------------------- /src/operations/shared-steps.ts: -------------------------------------------------------------------------------- ```typescript import { SharedStepUpdate } from 'qaseio'; import { z } from 'zod'; import { client, toResult } from '../utils.js'; import { pipe } from 'ramda'; export const GetSharedStepsSchema = z.object({ code: z.string(), search: z.string().optional(), limit: z.number().optional(), offset: z.number().optional(), }); export const GetSharedStepSchema = z.object({ code: z.string(), hash: z.string(), }); export const CreateSharedStepSchema = z.object({ code: z.string(), title: z.string(), action: z.string(), expected_result: z.string().optional(), data: z.string().optional(), steps: z .array( z.object({ action: z.string(), expected_result: z.string().optional(), data: z.string().optional(), position: z.number().optional(), }), ) .optional(), }); export const UpdateSharedStepSchema = z .object({ code: z.string(), hash: z.string(), title: z.string(), action: z.string(), expected_result: z.string().optional(), data: z.string().optional(), steps: z .array( z.object({ action: z.string(), expected_result: z.string().optional(), data: z.string().optional(), position: z.number().optional(), }), ) .optional(), }) .transform((data) => ({ code: data.code, hash: data.hash, stepData: { title: data.title, action: data.action, expected_result: data.expected_result, data: data.data, steps: data.steps, } as SharedStepUpdate, })); export const getSharedSteps = pipe( client.sharedSteps.getSharedSteps.bind(client.sharedSteps), toResult, ); export const getSharedStep = pipe( client.sharedSteps.getSharedStep.bind(client.sharedSteps), toResult, ); export const createSharedStep = pipe( client.sharedSteps.createSharedStep.bind(client.sharedSteps), toResult, ); export const updateSharedStep = pipe( client.sharedSteps.updateSharedStep.bind(client.sharedSteps), toResult, ); ``` -------------------------------------------------------------------------------- /src/operations/cases.ts: -------------------------------------------------------------------------------- ```typescript import { TestCaseCreate } from 'qaseio'; import { z } from 'zod'; import { client, toResult } from '../utils.js'; import { apply, pipe } from 'ramda'; export const GetCasesSchema = z.object({ code: z.string(), search: z.string().optional(), milestoneId: z.number().optional(), suiteId: z.number().optional(), severity: z.string().optional(), priority: z.string().optional(), type: z.string().optional(), behavior: z.string().optional(), automation: z.string().optional(), status: z.string().optional(), externalIssuesType: z .enum([ 'asana', 'azure-devops', 'clickup-app', 'github-app', 'gitlab-app', 'jira-cloud', 'jira-server', 'linear', 'monday', 'redmine-app', 'trello-app', 'youtrack-app', ]) .optional(), externalIssuesIds: z.array(z.string()).optional(), include: z.string().optional(), limit: z.number().optional(), offset: z.number().optional(), }); export const GetCaseSchema = z.object({ code: z.string(), id: z.number(), }); export const CreateCaseSchema = z.object({ code: z.string(), testCase: z.record(z.any()).transform((v) => v as TestCaseCreate), }); export const UpdateCaseSchema = z.object({ code: z.string(), id: z.number(), title: z.string().optional(), description: z.string().optional(), preconditions: z.string().optional(), postconditions: z.string().optional(), severity: z.number().optional(), priority: z.number().optional(), type: z.number().optional(), behavior: z.number().optional(), automation: z.number().optional(), status: z.number().optional(), suite_id: z.number().optional(), milestone_id: z.number().optional(), layer: z.number().optional(), is_flaky: z.boolean().optional(), params: z .array( z.object({ title: z.string(), value: z.string(), }), ) .optional(), tags: z.array(z.string()).optional(), steps: z .array( z.object({ action: z.string(), expected_result: z.string().optional(), data: z.string().optional(), position: z.number().optional(), }), ) .optional(), custom_fields: z .array( z.object({ id: z.number(), value: z.string(), }), ) .optional(), }); export const CreateCaseBulkSchema = z.object({ code: z.string(), cases: z.array( z.object({ title: z.string(), description: z.string().optional(), preconditions: z.string().optional(), postconditions: z.string().optional(), severity: z.number().optional(), priority: z.number().optional(), type: z.number().optional(), behavior: z.number().optional(), automation: z.number().optional(), status: z.number().optional(), suite_id: z.number().optional(), milestone_id: z.number().optional(), layer: z.number().optional(), is_flaky: z.boolean().optional(), params: z .array( z.object({ title: z.string(), value: z.string(), }), ) .optional(), tags: z.array(z.string()).optional(), steps: z .array( z.object({ action: z.string(), expected_result: z.string().optional(), data: z.string().optional(), position: z.number().optional(), }), ) .optional(), custom_fields: z .array( z.object({ id: z.number(), value: z.string(), }), ) .optional(), }), ), }); export const getCases = pipe( apply(client.cases.getCases.bind(client.cases)), toResult, ); export const getCase = pipe(client.cases.getCase.bind(client.cases), toResult); export const createCase = pipe( client.cases.createCase.bind(client.cases), toResult, ); const convertCaseData = ( data: Omit<z.infer<typeof UpdateCaseSchema>, 'code' | 'id'>, ) => ({ ...data, is_flaky: data.is_flaky === undefined ? undefined : data.is_flaky ? 1 : 0, params: data.params ? data.params.reduce( (acc, param) => ({ ...acc, [param.title]: [param.value], }), {}, ) : undefined, }); export const updateCase = pipe( ( code: string, id: number, data: Omit<z.infer<typeof UpdateCaseSchema>, 'code' | 'id'>, ) => client.cases.updateCase(code, id, convertCaseData(data)), toResult, ); ``` -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- ```typescript #!/usr/bin/env node /** * This is a template MCP server that implements a simple notes system. * It demonstrates core MCP concepts like resources and tools by allowing: * - Listing notes as resources * - Reading individual notes * - Creating new notes via a tool * - Summarizing all notes via a prompt */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import { zodToJsonSchema } from 'zod-to-json-schema'; import { listProjects, getProject, createProject, CreateProjectSchema, GetProjectSchema, ListProjectsSchema, } from './operations/projects.js'; import { getResults, getResult, createResult, CreateResultSchema, GetResultSchema, GetResultsSchema, CreateResultBulkSchema, createResultBulk, UpdateResultSchema, updateResult, } from './operations/results.js'; import { getCases, getCase, createCase, updateCase, GetCasesSchema, GetCaseSchema, CreateCaseSchema, UpdateCaseSchema, } from './operations/cases.js'; import { getRuns, getRun, GetRunsSchema, GetRunSchema, } from './operations/runs.js'; import { getPlans, getPlan, createPlan, updatePlan, GetPlansSchema, GetPlanSchema, CreatePlanSchema, UpdatePlanSchema, } from './operations/plans.js'; import { GetSuitesSchema, GetSuiteSchema, CreateSuiteSchema, UpdateSuiteSchema, getSuites, getSuite, createSuite, updateSuite, } from './operations/suites.js'; import { GetSharedStepsSchema, GetSharedStepSchema, CreateSharedStepSchema, UpdateSharedStepSchema, getSharedSteps, getSharedStep, createSharedStep, updateSharedStep, } from './operations/shared-steps.js'; import { match } from 'ts-pattern'; import { errAsync } from 'neverthrow'; /** * Create an MCP server with capabilities for resources (to list/read notes), * tools (to create new notes), and prompts (to summarize notes). */ const server = new Server( { name: 'mcp-qase', version: '0.1.0', }, { capabilities: { resources: {}, tools: {}, prompts: {}, }, }, ); /** * Handler for listing available notes as resources. */ server.setRequestHandler(ListResourcesRequestSchema, () => ({ resources: [], })); /** * Handler for reading the contents */ server.setRequestHandler(ReadResourceRequestSchema, () => ({ contents: [], })); /** * Handler that lists available tools. * Exposes a single "create_note" tool that lets clients create new notes. */ server.setRequestHandler(ListToolsRequestSchema, () => ({ tools: [ { name: 'list_projects', description: 'Get All Projects', inputSchema: zodToJsonSchema(ListProjectsSchema), }, { name: 'get_project', description: 'Get project by code', inputSchema: zodToJsonSchema(GetProjectSchema), }, { name: 'create_project', description: 'Create new project', inputSchema: zodToJsonSchema(CreateProjectSchema), }, { name: 'get_results', description: 'Get all test run results for a project', inputSchema: zodToJsonSchema(GetResultsSchema), }, { name: 'get_result', description: 'Get test run result by code and hash', inputSchema: zodToJsonSchema(GetResultSchema), }, { name: 'create_result', description: 'Create test run result', inputSchema: zodToJsonSchema(CreateResultSchema), }, { name: 'create_result_bulk', description: 'Create multiple test run results in bulk', inputSchema: zodToJsonSchema(CreateResultBulkSchema), }, { name: 'update_result', description: 'Update an existing test run result', inputSchema: zodToJsonSchema(UpdateResultSchema), }, { name: 'get_cases', description: 'Get all test cases in a project', inputSchema: zodToJsonSchema(GetCasesSchema), }, { name: 'get_case', description: 'Get a specific test case', inputSchema: zodToJsonSchema(GetCaseSchema), }, { name: 'create_case', description: 'Create a new test case', inputSchema: zodToJsonSchema(CreateCaseSchema), }, { name: 'update_case', description: 'Update an existing test case', inputSchema: zodToJsonSchema(UpdateCaseSchema), }, { name: 'get_runs', description: 'Get all test runs in a project', inputSchema: zodToJsonSchema(GetRunsSchema), }, { name: 'get_run', description: 'Get a specific test run', inputSchema: zodToJsonSchema(GetRunSchema), }, { name: 'get_plans', description: 'Get all test plans in a project', inputSchema: zodToJsonSchema(GetPlansSchema), }, { name: 'get_plan', description: 'Get a specific test plan', inputSchema: zodToJsonSchema(GetPlanSchema), }, { name: 'create_plan', description: 'Create a new test plan', inputSchema: zodToJsonSchema(CreatePlanSchema), }, { name: 'update_plan', description: 'Update an existing test plan', inputSchema: zodToJsonSchema(UpdatePlanSchema), }, { name: 'get_suites', description: 'Get all test suites in a project', inputSchema: zodToJsonSchema(GetSuitesSchema), }, { name: 'get_suite', description: 'Get a specific test suite', inputSchema: zodToJsonSchema(GetSuiteSchema), }, { name: 'create_suite', description: 'Create a new test suite', inputSchema: zodToJsonSchema(CreateSuiteSchema), }, { name: 'update_suite', description: 'Update an existing test suite', inputSchema: zodToJsonSchema(UpdateSuiteSchema), }, { name: 'get_shared_steps', description: 'Get all shared steps in a project', inputSchema: zodToJsonSchema(GetSharedStepsSchema), }, { name: 'get_shared_step', description: 'Get a specific shared step', inputSchema: zodToJsonSchema(GetSharedStepSchema), }, { name: 'create_shared_step', description: 'Create a new shared step', inputSchema: zodToJsonSchema(CreateSharedStepSchema), }, { name: 'update_shared_step', description: 'Update an existing shared step', inputSchema: zodToJsonSchema(UpdateSharedStepSchema), }, ], })); /** * Handler for the create_note tool. * Creates a new note with the provided title and content, and returns success message. */ server.setRequestHandler(CallToolRequestSchema, (request) => match(request.params) .with({ name: 'list_projects' }, ({ arguments: args }) => { const { limit, offset } = ListProjectsSchema.parse(args); return listProjects(limit, offset); }) .with({ name: 'get_project' }, ({ arguments: args }) => { const { code } = GetProjectSchema.parse(args); return getProject(code); }) .with({ name: 'create_project' }, ({ arguments: args }) => { const parsedArgs = CreateProjectSchema.parse(args); return createProject(parsedArgs); }) .with({ name: 'get_results' }, ({ arguments: args }) => { const parsedArgs = GetResultsSchema.parse(args); const filters = parsedArgs.status || parsedArgs.from || parsedArgs.to ? `status=${parsedArgs.status || ''}&from=${parsedArgs.from || ''}&to=${parsedArgs.to || ''}` : undefined; return getResults([ parsedArgs.code, parsedArgs.limit, parsedArgs.offset, filters, ]); }) .with({ name: 'get_result' }, ({ arguments: args }) => { const { code, hash } = GetResultSchema.parse(args); return getResult(code, hash); }) .with({ name: 'create_result' }, ({ arguments: args }) => { const { code, id, result } = CreateResultSchema.parse(args); return createResult(code, id, result); }) .with({ name: 'create_result_bulk' }, ({ arguments: args }) => { const { code, id, results } = CreateResultBulkSchema.parse(args); return createResultBulk(code, id, results); }) .with({ name: 'update_result' }, ({ arguments: args }) => { const { code, id, hash, result } = UpdateResultSchema.parse(args); return updateResult(code, id, hash, result); }) .with({ name: 'get_cases' }, ({ arguments: args }) => { const { code, search, milestoneId, suiteId, severity, priority, type, behavior, automation, status, externalIssuesType, externalIssuesIds, include, limit, offset, } = GetCasesSchema.parse(args); return getCases([ code, search, milestoneId, suiteId, severity, priority, type, behavior, automation, status, externalIssuesType, externalIssuesIds, include, limit, offset, ]); }) .with({ name: 'get_case' }, ({ arguments: args }) => { const { code, id } = GetCaseSchema.parse(args); return getCase(code, id); }) .with({ name: 'create_case' }, ({ arguments: args }) => { const { code, testCase } = CreateCaseSchema.parse(args); return createCase(code, testCase); }) .with({ name: 'update_case' }, ({ arguments: args }) => { const { code, id, ...caseData } = UpdateCaseSchema.parse(args); return updateCase(code, id, caseData); }) .with({ name: 'get_runs' }, ({ arguments: args }) => { const { code, search, status, milestone, environment, fromStartTime, toStartTime, limit, offset, include, } = GetRunsSchema.parse(args); return getRuns([ code, search, status, milestone, environment, fromStartTime, toStartTime, limit, offset, include, ]); }) .with({ name: 'get_run' }, ({ arguments: args }) => { const { code, id, include } = GetRunSchema.parse(args); return getRun(code, id, include); }) .with({ name: 'get_plans' }, ({ arguments: args }) => { const { code, limit, offset } = GetPlansSchema.parse(args); return getPlans(code, limit, offset); }) .with({ name: 'get_plan' }, ({ arguments: args }) => { const { code, id } = GetPlanSchema.parse(args); return getPlan(code, id); }) .with({ name: 'create_plan' }, ({ arguments: args }) => { const { code, ...planData } = CreatePlanSchema.parse(args); return createPlan(code, planData); }) .with({ name: 'update_plan' }, ({ arguments: args }) => { const { code, id, ...planData } = UpdatePlanSchema.parse(args); return updatePlan(code, id, planData); }) .with({ name: 'get_suites' }, ({ arguments: args }) => { const { code, search, limit, offset } = GetSuitesSchema.parse(args); return getSuites(code, search, limit, offset); }) .with({ name: 'get_suite' }, ({ arguments: args }) => { const { code, id } = GetSuiteSchema.parse(args); return getSuite(code, id); }) .with({ name: 'create_suite' }, ({ arguments: args }) => { const { code, ...suiteData } = CreateSuiteSchema.parse(args); return createSuite(code, suiteData); }) .with({ name: 'update_suite' }, ({ arguments: args }) => { const { code, id, ...suiteData } = UpdateSuiteSchema.parse(args); return updateSuite(code, id, suiteData); }) .with({ name: 'get_shared_steps' }, ({ arguments: args }) => { const { code, search, limit, offset } = GetSharedStepsSchema.parse(args); return getSharedSteps(code, search, limit, offset); }) .with({ name: 'get_shared_step' }, ({ arguments: args }) => { const { code, hash } = GetSharedStepSchema.parse(args); return getSharedStep(code, hash); }) .with({ name: 'create_shared_step' }, ({ arguments: args }) => { const { code, ...stepData } = CreateSharedStepSchema.parse(args); return createSharedStep(code, stepData); }) .with({ name: 'update_shared_step' }, ({ arguments: args }) => { const { code, hash, stepData } = UpdateSharedStepSchema.parse(args); return updateSharedStep(code, hash, stepData); }) .otherwise(() => errAsync('Unknown tool')) .map((response) => response.data.result) .map((data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], })) .match( (data) => data, (error) => { throw new Error(error); }, ), ); /** * Handler that lists available prompts. * Exposes a single "summarize_notes" prompt that summarizes all notes. */ server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: [], })); /** * Handler for the summarize_notes prompt. * Returns a prompt that requests summarization of all notes, with the notes' contents embedded as resources. */ server.setRequestHandler(GetPromptRequestSchema, async () => ({ messages: [], })); /** * Start the server using stdio transport. * This allows the server to communicate via standard input/output streams. */ async function main() { const transport = new StdioServerTransport(); await server.connect(transport); } main().catch((error) => { console.error('Server error:', error); process.exit(1); }); ```