wopee-mcp 1.27.0 → 1.29.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,14 +7,20 @@ import { wopeeCreateBlankSuite } from "./wopee_create_blank_suite/index.js";
7
7
  import { wopeeFetchAnalysisSuites } from "./wopee_fetch_analysis_suites/index.js";
8
8
  import { wopeeFetchExecutedTestCases } from "./wopee_fetch_executed_test_cases/index.js";
9
9
  import { wopeeFetchRecentExecutions } from "./wopee_fetch_recent_executions/index.js";
10
+ import { wopeeFetchTestInventory } from "./wopee_fetch_test_inventory/index.js";
10
11
  import { wopeeSendChatMessage } from "./wopee_send_chat_message/index.js";
11
12
  import { wopeeReadChatHistory } from "./wopee_read_chat_history/index.js";
12
13
  import { wopeeCreateGithubIssue } from "./wopee_create_github_issue/index.js";
14
+ import { wopeeFetchVariables } from "./wopee_fetch_variables/index.js";
15
+ import { wopeeUpdateVariables } from "./wopee_update_variables/index.js";
13
16
  export const TOOLS = [
14
17
  wopeeCreateBlankSuite,
15
18
  wopeeFetchAnalysisSuites,
16
19
  wopeeFetchExecutedTestCases,
17
20
  wopeeFetchRecentExecutions,
21
+ wopeeFetchTestInventory,
22
+ wopeeFetchVariables,
23
+ wopeeUpdateVariables,
18
24
  wopeeDispatchAnalysis,
19
25
  wopeeDispatchAgent,
20
26
  wopeeFetchArtifact,
@@ -13,6 +13,26 @@ export const UpdateArtifact = `
13
13
  updateArtifact(input: $input)
14
14
  }
15
15
  `;
16
+ export const FetchProjectVariables = `
17
+ query FetchProjectVariables($projectUuid: ID!) {
18
+ fetchProjectVariables(projectUuid: $projectUuid)
19
+ }
20
+ `;
21
+ export const FetchSuiteVariables = `
22
+ query FetchSuiteVariables($suiteUuid: ID!) {
23
+ fetchSuiteVariables(suiteUuid: $suiteUuid)
24
+ }
25
+ `;
26
+ export const UpdateProjectVariables = `
27
+ mutation UpdateProjectVariables($input: UpdateProjectVariablesInput!) {
28
+ updateProjectVariables(input: $input)
29
+ }
30
+ `;
31
+ export const UpdateSuiteVariables = `
32
+ mutation UpdateSuiteVariables($suiteUuid: ID!, $additionalVariables: String!) {
33
+ updateSuiteVariables(suiteUuid: $suiteUuid, additionalVariables: $additionalVariables)
34
+ }
35
+ `;
16
36
  export const DispatchAgent = `
17
37
  mutation DispatchAgent(
18
38
  $input: TestCasesInput!
@@ -14,6 +14,18 @@ export const SuiteAnalysisConfigSchema = z.object({
14
14
  additionalInstructions: z.string().nullable().default(null),
15
15
  additionalVariables: z.string().nullable().default(null),
16
16
  });
17
+ // Shared by wopee_dispatch_analysis and wopee_update_variables. Keys must be
18
+ // uppercase (server enforces the same EnvVarKeyRegex on write).
19
+ export const AdditionalVariableSchema = z.object({
20
+ key: z
21
+ .string({
22
+ description: "Variable name. Must be uppercase with underscores only (e.g. MY_VAR, BASE_URL).",
23
+ })
24
+ .regex(/^[A-Z_][A-Z0-9_]*$/, "Key must be uppercase alphanumeric with underscores, starting with a letter or underscore"),
25
+ value: z
26
+ .string({ description: "Variable value. Must be a non-empty string." })
27
+ .min(1, "Value must be a non-empty string"),
28
+ });
17
29
  export const GenerateAIDataHandlerInputSchema = z.object({
18
30
  type: z.nativeEnum(GenerateArtifactType, {
19
31
  description: "Type of test artifact to generate. One of: APP_CONTEXT, GENERAL_USER_STORIES, USER_STORIES_WITH_TEST_CASES, TEST_CASES, TEST_CASE_STEPS, REUSABLE_TEST_CASES, REUSABLE_TEST_CASE_STEPS. Start with APP_CONTEXT, then generate stories and test cases from it.",
@@ -4,6 +4,7 @@ export var ToolName;
4
4
  ToolName["WOPEE_FETCH_ANALYSIS_SUITES"] = "wopee_fetch_analysis_suites";
5
5
  ToolName["WOPEE_FETCH_EXECUTED_TEST_CASES"] = "wopee_fetch_executed_test_cases";
6
6
  ToolName["WOPEE_FETCH_RECENT_EXECUTIONS"] = "wopee_fetch_recent_executions";
7
+ ToolName["WOPEE_FETCH_TEST_INVENTORY"] = "wopee_fetch_test_inventory";
7
8
  ToolName["WOPEE_DISPATCH_ANALYSIS"] = "wopee_dispatch_analysis";
8
9
  ToolName["WOPEE_DISPATCH_AGENT"] = "wopee_dispatch_agent";
9
10
  ToolName["WOPEE_FETCH_ARTIFACT"] = "wopee_fetch_artifact";
@@ -12,7 +13,14 @@ export var ToolName;
12
13
  ToolName["WOPEE_SEND_CHAT_MESSAGE"] = "wopee_send_chat_message";
13
14
  ToolName["WOPEE_READ_CHAT_HISTORY"] = "wopee_read_chat_history";
14
15
  ToolName["WOPEE_CREATE_GITHUB_ISSUE"] = "wopee_create_github_issue";
16
+ ToolName["WOPEE_FETCH_VARIABLES"] = "wopee_fetch_variables";
17
+ ToolName["WOPEE_UPDATE_VARIABLES"] = "wopee_update_variables";
15
18
  })(ToolName || (ToolName = {}));
19
+ export var VariableLevel;
20
+ (function (VariableLevel) {
21
+ VariableLevel["PROJECT"] = "PROJECT";
22
+ VariableLevel["ANALYSIS"] = "ANALYSIS";
23
+ })(VariableLevel || (VariableLevel = {}));
16
24
  export var GenerateArtifactType;
17
25
  (function (GenerateArtifactType) {
18
26
  GenerateArtifactType["APP_CONTEXT"] = "APP_CONTEXT";
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
- import { SuiteAnalysisConfigSchema } from "../shared/schemas.js";
2
+ import { SuiteAnalysisConfigSchema, AdditionalVariableSchema, } from "../shared/schemas.js";
3
3
  var RerunMode;
4
4
  (function (RerunMode) {
5
5
  RerunMode["FULL"] = "FULL";
@@ -15,16 +15,6 @@ export const DispatchAnalysisInputSchema = z.object({
15
15
  suiteAnalysisConfig: SuiteAnalysisConfigSchema,
16
16
  rerun: RerunOptionsSchema.nullable().default(null),
17
17
  });
18
- const AdditionalVariableSchema = z.object({
19
- key: z
20
- .string({
21
- description: "Variable name. Must be uppercase with underscores only (e.g. MY_VAR, BASE_URL).",
22
- })
23
- .regex(/^[A-Z_][A-Z0-9_]*$/, "Key must be uppercase alphanumeric with underscores, starting with a letter or underscore"),
24
- value: z
25
- .string({ description: "Variable value. Must be a non-empty string." })
26
- .min(1, "Value must be a non-empty string"),
27
- });
28
18
  const WopeeRerunOptionsSchema = z.object({
29
19
  suiteUuid: z
30
20
  .string({
@@ -0,0 +1,83 @@
1
+ import { getConfig } from "../../utils/getConfig.js";
2
+ import { requestClient } from "../../utils/requestClient.js";
3
+ import { _parseError } from "../shared/helpers.js";
4
+ import { ArtifactType, ToolName, } from "../shared/types.js";
5
+ import { FetchAnalysisSuites, FetchArtifact, FetchExecutedTestCases, } from "../shared/gql-queries.js";
6
+ import { WopeeFetchTestInventoryInputSchema, } from "./schema.js";
7
+ import { buildTestInventory } from "./logic.js";
8
+ export const wopeeFetchTestInventory = {
9
+ name: ToolName.WOPEE_FETCH_TEST_INVENTORY,
10
+ config: {
11
+ title: "Fetch test inventory (counts + statuses)",
12
+ description: "The authoritative tool for how many tests exist and their latest status. Returns, per analysis, the FULL list of authored test cases joined with their latest execution status — including never-run ones as NOT_RUN. Use this for questions like 'how many tests do I have', 'list the scenarios/test cases in A001', or 'show executed and not-run tests in one table'. Terminology: a 'scenario' is a test case; test cases are grouped under user stories (US001) and identified as US001:TC001. Reusable blocks (user story R001) are counted separately (reusableBlockCount) and are building blocks, not runnable, so they never carry an execution status. Regular tests are all non-R001 test cases. Read-only. Takes an optional analysisIdentifier (e.g. A001) to scope to one analysis; omit to cover every analysis in the project. Prefer this over wopee_fetch_recent_executions / wopee_fetch_executed_test_cases when the user asks about totals or the complete list — those return only test cases that have already run.",
13
+ inputSchema: WopeeFetchTestInventoryInputSchema.shape,
14
+ },
15
+ handler: async (input) => {
16
+ try {
17
+ const { WOPEE_PROJECT_UUID } = getConfig();
18
+ if (!WOPEE_PROJECT_UUID)
19
+ return {
20
+ content: [
21
+ { type: "text", text: "WOPEE_PROJECT_UUID is not set" },
22
+ ],
23
+ };
24
+ const parsed = WopeeFetchTestInventoryInputSchema.parse(input ?? {});
25
+ const suitesResult = await requestClient(FetchAnalysisSuites, { projectUuid: WOPEE_PROJECT_UUID });
26
+ let suites = suitesResult?.fetchAnalysisSuites ?? [];
27
+ if (parsed.analysisIdentifier)
28
+ suites = suites.filter((s) => s.analysisIdentifier === parsed.analysisIdentifier);
29
+ if (suites.length === 0)
30
+ return {
31
+ content: [
32
+ {
33
+ type: "text",
34
+ text: parsed.analysisIdentifier
35
+ ? `No analysis found with identifier ${parsed.analysisIdentifier}.`
36
+ : "No analyses found in this project yet.",
37
+ },
38
+ ],
39
+ };
40
+ // Fetch the authored test cases (USER_STORIES artifact) and execution
41
+ // history for every analysis in parallel. A failure on one suite (e.g. an
42
+ // analysis with no generated tests yet) degrades to an empty result for
43
+ // that suite rather than failing the whole inventory.
44
+ const inputs = await Promise.all(suites.map(async (suite) => {
45
+ const [artifactRes, execRes] = await Promise.allSettled([
46
+ requestClient(FetchArtifact, {
47
+ input: {
48
+ projectUuid: WOPEE_PROJECT_UUID,
49
+ suiteUuid: suite.uuid,
50
+ type: ArtifactType.USER_STORIES,
51
+ ref: suite.analysisIdentifier ?? undefined,
52
+ },
53
+ }),
54
+ requestClient(FetchExecutedTestCases, {
55
+ input: {
56
+ projectUuid: WOPEE_PROJECT_UUID,
57
+ analysisSuiteUuid: suite.uuid,
58
+ analysisIdentifier: suite.analysisIdentifier ?? "",
59
+ },
60
+ }),
61
+ ]);
62
+ return {
63
+ suite,
64
+ artifactContent: artifactRes.status === "fulfilled"
65
+ ? (artifactRes.value?.fetchArtifact?.content ?? null)
66
+ : null,
67
+ execGroups: execRes.status === "fulfilled"
68
+ ? (execRes.value?.fetchExecutedTestCases ?? [])
69
+ : [],
70
+ };
71
+ }));
72
+ const inventory = buildTestInventory(inputs);
73
+ return {
74
+ content: [
75
+ { type: "text", text: JSON.stringify(inventory, null, 2) },
76
+ ],
77
+ };
78
+ }
79
+ catch (error) {
80
+ return _parseError(error);
81
+ }
82
+ },
83
+ };
@@ -0,0 +1,115 @@
1
+ // The special user story that holds reusable blocks (building blocks referenced
2
+ // by other test cases' steps, not independently runnable). Forced onto reusable
3
+ // test cases at generation time (api generateReusableTestCases.ts).
4
+ export const REUSABLE_USER_STORY_ID = "R001";
5
+ export const NOT_RUN = "NOT_RUN";
6
+ /** Parse the USER_STORIES artifact JSON; tolerant of null/empty/malformed. */
7
+ export function parseUserStories(content) {
8
+ if (!content)
9
+ return [];
10
+ try {
11
+ const parsed = JSON.parse(content);
12
+ const stories = parsed?.userStories;
13
+ return Array.isArray(stories) ? stories : [];
14
+ }
15
+ catch {
16
+ return [];
17
+ }
18
+ }
19
+ /**
20
+ * Index every execution by `${userStoryId}::${testCaseId}`, newest first.
21
+ * Sorted by createdAt descending so index[key][0] is the latest run.
22
+ */
23
+ export function indexExecutions(groups) {
24
+ const map = new Map();
25
+ for (const grp of groups ?? []) {
26
+ for (const etc of grp.executedTestCases ?? []) {
27
+ const key = `${etc.userStoryId}::${etc.testCaseId}`;
28
+ const list = map.get(key);
29
+ if (list)
30
+ list.push(etc);
31
+ else
32
+ map.set(key, [etc]);
33
+ }
34
+ }
35
+ for (const list of map.values())
36
+ list.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
37
+ return map;
38
+ }
39
+ /**
40
+ * Latest-run verdict, matching CMD rowVerdict precedence: the agent report is
41
+ * the trace-verified verdict, then the code report, then the raw execution
42
+ * status, else "not run". Report statuses (PASSED/FAILED/INCOMPLETE) win over
43
+ * a bare FINISHED — a FINISHED run can still be a FAILED verdict.
44
+ */
45
+ export function resolveStatus(exec) {
46
+ if (!exec)
47
+ return NOT_RUN;
48
+ const report = exec.agentReportStatus || exec.codeReportStatus;
49
+ if (report)
50
+ return report;
51
+ return exec.executionStatus ?? NOT_RUN;
52
+ }
53
+ function buildAnalysisInventory({ suite, artifactContent, execGroups, }) {
54
+ const userStories = parseUserStories(artifactContent);
55
+ const execIndex = indexExecutions(execGroups);
56
+ const testCases = [];
57
+ const statusSummary = {};
58
+ let regularTestCount = 0;
59
+ let reusableBlockCount = 0;
60
+ for (const story of userStories) {
61
+ const isReusable = story.userStoryId === REUSABLE_USER_STORY_ID;
62
+ for (const tc of story.testCases ?? []) {
63
+ if (isReusable)
64
+ reusableBlockCount += 1;
65
+ else
66
+ regularTestCount += 1;
67
+ // Reusable blocks are not independently runnable, so they never carry an
68
+ // execution status.
69
+ const exec = isReusable
70
+ ? undefined
71
+ : execIndex.get(`${story.userStoryId}::${tc.testCaseId}`)?.[0];
72
+ const status = resolveStatus(exec);
73
+ if (!isReusable)
74
+ statusSummary[status] = (statusSummary[status] ?? 0) + 1;
75
+ testCases.push({
76
+ id: `${story.userStoryId}:${tc.testCaseId}`,
77
+ userStoryId: story.userStoryId,
78
+ userStoryName: story.userStoryName ?? "",
79
+ userStoryCategory: story.userStoryCategory ?? "",
80
+ testCaseId: tc.testCaseId,
81
+ name: tc.name ?? "",
82
+ kind: isReusable ? "REUSABLE" : "REGULAR",
83
+ status,
84
+ executedAt: exec?.updatedAt ?? exec?.createdAt ?? null,
85
+ });
86
+ }
87
+ }
88
+ return {
89
+ analysisIdentifier: suite.analysisIdentifier,
90
+ suiteUuid: suite.uuid,
91
+ name: suite.name,
92
+ suiteRunningStatus: suite.suiteRunningStatus,
93
+ regularTestCount,
94
+ reusableBlockCount,
95
+ statusSummary,
96
+ testCases,
97
+ };
98
+ }
99
+ /**
100
+ * Assemble the full test inventory across analyses: regular vs reusable counts,
101
+ * and every authored test case with its latest execution status (never-run =
102
+ * NOT_RUN). This is the single deterministic answer for "how many tests",
103
+ * "list scenarios", and "executed + not-run in one list".
104
+ */
105
+ export function buildTestInventory(inputs) {
106
+ const analyses = inputs.map(buildAnalysisInventory);
107
+ return {
108
+ totals: {
109
+ analyses: analyses.length,
110
+ regularTests: analyses.reduce((n, a) => n + a.regularTestCount, 0),
111
+ reusableBlocks: analyses.reduce((n, a) => n + a.reusableBlockCount, 0),
112
+ },
113
+ analyses,
114
+ };
115
+ }
@@ -0,0 +1,8 @@
1
+ import { z } from "zod";
2
+ export const WopeeFetchTestInventoryInputSchema = z.object({
3
+ analysisIdentifier: z
4
+ .string({
5
+ description: "Optional analysis identifier (e.g. A001) to scope the inventory to a single analysis. Omit to include every analysis in the project.",
6
+ })
7
+ .optional(),
8
+ });
@@ -0,0 +1,36 @@
1
+ import { getConfig } from "../../utils/getConfig.js";
2
+ import { requestClient } from "../../utils/requestClient.js";
3
+ import { _parseError } from "../shared/helpers.js";
4
+ import { ToolName, VariableLevel } from "../shared/types.js";
5
+ import { FetchProjectVariables, FetchSuiteVariables, } from "../shared/gql-queries.js";
6
+ import { WopeeFetchVariablesInputSchema, } from "./schema.js";
7
+ const textResult = (text) => ({
8
+ content: [{ type: "text", text }],
9
+ });
10
+ export const wopeeFetchVariables = {
11
+ name: ToolName.WOPEE_FETCH_VARIABLES,
12
+ config: {
13
+ title: "Fetch run-time variables",
14
+ description: "Read the run-time variables (additionalVariables) that drive analysis/agent runs, at either level. level: PROJECT returns the project-level variables (uses WOPEE_PROJECT_UUID from the environment); level: ANALYSIS returns a specific analysis suite's variables and requires suiteUuid. Read-only. Returns a JSON string array of { key, value, sourceType } entries, or [] when none are set. Use wopee_fetch_analysis_suites to discover suite UUIDs.",
15
+ inputSchema: WopeeFetchVariablesInputSchema.shape,
16
+ },
17
+ handler: async (input) => {
18
+ try {
19
+ const { level, suiteUuid } = input;
20
+ if (level === VariableLevel.ANALYSIS) {
21
+ if (!suiteUuid)
22
+ return textResult("suiteUuid is required when level is ANALYSIS");
23
+ const result = await requestClient(FetchSuiteVariables, { suiteUuid });
24
+ return textResult(result?.fetchSuiteVariables ?? "[]");
25
+ }
26
+ const { WOPEE_PROJECT_UUID } = getConfig();
27
+ if (!WOPEE_PROJECT_UUID)
28
+ return textResult("WOPEE_PROJECT_UUID is not set");
29
+ const result = await requestClient(FetchProjectVariables, { projectUuid: WOPEE_PROJECT_UUID });
30
+ return textResult(result?.fetchProjectVariables ?? "[]");
31
+ }
32
+ catch (error) {
33
+ return _parseError(error);
34
+ }
35
+ },
36
+ };
@@ -0,0 +1,12 @@
1
+ import { z } from "zod";
2
+ import { VariableLevel } from "../shared/types.js";
3
+ export const WopeeFetchVariablesInputSchema = z.object({
4
+ level: z.nativeEnum(VariableLevel, {
5
+ description: "Which variable set to read. PROJECT reads the project-level variables (uses WOPEE_PROJECT_UUID from the environment). ANALYSIS reads a specific analysis suite's variables and requires suiteUuid.",
6
+ }),
7
+ suiteUuid: z
8
+ .string({
9
+ description: "UUID of the analysis suite to read variables from. Required when level is ANALYSIS; ignored when level is PROJECT.",
10
+ })
11
+ .nullish(),
12
+ });
@@ -0,0 +1,24 @@
1
+ import { getConfig } from "../../utils/getConfig.js";
2
+ /**
3
+ * Wire payload for the updateSuiteVariables mutation. The handler guarantees
4
+ * suiteUuid is present before calling this (ANALYSIS level).
5
+ */
6
+ export const createUpdateSuiteVariablesInput = (input) => ({
7
+ suiteUuid: input.suiteUuid,
8
+ additionalVariables: JSON.stringify(input.variables),
9
+ });
10
+ /**
11
+ * Wire payload for the updateProjectVariables mutation, injecting
12
+ * WOPEE_PROJECT_UUID from the environment (PROJECT level).
13
+ */
14
+ export const createUpdateProjectVariablesInput = (input) => {
15
+ const { WOPEE_PROJECT_UUID } = getConfig();
16
+ if (!WOPEE_PROJECT_UUID)
17
+ throw new Error("WOPEE_PROJECT_UUID is not set");
18
+ return {
19
+ input: {
20
+ projectUuid: WOPEE_PROJECT_UUID,
21
+ additionalVariables: JSON.stringify(input.variables),
22
+ },
23
+ };
24
+ };
@@ -0,0 +1,38 @@
1
+ import { requestClient } from "../../utils/requestClient.js";
2
+ import { withRetry } from "../../utils/withRetry.js";
3
+ import { _parseError } from "../shared/helpers.js";
4
+ import { ToolName, VariableLevel } from "../shared/types.js";
5
+ import { UpdateProjectVariables, UpdateSuiteVariables, } from "../shared/gql-queries.js";
6
+ import { createUpdateProjectVariablesInput, createUpdateSuiteVariablesInput, } from "./factory.js";
7
+ import { WopeeUpdateVariablesInputSchema, } from "./schema.js";
8
+ const textResult = (text) => ({
9
+ content: [{ type: "text", text }],
10
+ });
11
+ export const wopeeUpdateVariables = {
12
+ name: ToolName.WOPEE_UPDATE_VARIABLES,
13
+ config: {
14
+ title: "Update run-time variables",
15
+ description: "Upsert the run-time variables (additionalVariables) that drive analysis/agent runs, at either level. level: PROJECT writes the project-level variables (uses WOPEE_PROJECT_UUID from the environment); level: ANALYSIS writes a specific analysis suite's variables and requires suiteUuid. Merge semantics: keys in variables[] are added or overwritten, existing keys not listed are preserved. Keys must be uppercase (e.g. BASE_URL); the server re-sanitizes and drops invalid keys. Returns a confirmation on success.",
16
+ inputSchema: WopeeUpdateVariablesInputSchema.shape,
17
+ },
18
+ handler: async (input) => {
19
+ try {
20
+ const { level, suiteUuid } = input;
21
+ if (level === VariableLevel.ANALYSIS) {
22
+ if (!suiteUuid)
23
+ return textResult("suiteUuid is required when level is ANALYSIS");
24
+ const result = await withRetry(() => requestClient(UpdateSuiteVariables, createUpdateSuiteVariablesInput(input)));
25
+ if (!result?.updateSuiteVariables)
26
+ return textResult("Failed to update suite variables");
27
+ return textResult("Suite variables updated successfully");
28
+ }
29
+ const result = await withRetry(() => requestClient(UpdateProjectVariables, createUpdateProjectVariablesInput(input)));
30
+ if (!result?.updateProjectVariables)
31
+ return textResult("Failed to update project variables");
32
+ return textResult("Project variables updated successfully");
33
+ }
34
+ catch (error) {
35
+ return _parseError(error);
36
+ }
37
+ },
38
+ };
@@ -0,0 +1,16 @@
1
+ import { z } from "zod";
2
+ import { VariableLevel } from "../shared/types.js";
3
+ import { AdditionalVariableSchema } from "../shared/schemas.js";
4
+ export const WopeeUpdateVariablesInputSchema = z.object({
5
+ level: z.nativeEnum(VariableLevel, {
6
+ description: "Which variable set to write. PROJECT writes the project-level variables (uses WOPEE_PROJECT_UUID from the environment). ANALYSIS writes a specific analysis suite's variables and requires suiteUuid.",
7
+ }),
8
+ suiteUuid: z
9
+ .string({
10
+ description: "UUID of the analysis suite to write variables to. Required when level is ANALYSIS; ignored when level is PROJECT.",
11
+ })
12
+ .nullish(),
13
+ variables: z.array(AdditionalVariableSchema, {
14
+ description: "Variables to upsert. Each needs an uppercase key (e.g. BASE_URL) and a non-empty value. Merge semantics: keys listed here are added or overwritten; existing keys not listed here are preserved.",
15
+ }),
16
+ });
package/package.json CHANGED
@@ -4,13 +4,14 @@
4
4
  "bin": {
5
5
  "wopee-mcp": "./build/index.js"
6
6
  },
7
- "version": "1.27.0",
7
+ "version": "1.29.0",
8
8
  "mcpName": "io.github.Wopee-io/wopee-mcp",
9
9
  "description": "Wopee.io MCP server for autonomous testing platform",
10
10
  "main": "./build/index.js",
11
11
  "scripts": {
12
12
  "build": "tsc && chmod 755 build/index.js",
13
- "build:clean": "rm -rf build && npm run build"
13
+ "build:clean": "rm -rf build && npm run build",
14
+ "test": "node --test \"src/**/*.test.ts\""
14
15
  },
15
16
  "files": [
16
17
  "build",