@smartbear/mcp 0.15.0 → 0.17.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.
Files changed (53) hide show
  1. package/dist/bugsnag/client/api/Error.js +43 -0
  2. package/dist/bugsnag/client.js +44 -1310
  3. package/dist/bugsnag/input-schemas.js +18 -18
  4. package/dist/bugsnag/tool/error/get-error.js +98 -0
  5. package/dist/bugsnag/tool/error/list-project-errors.js +102 -0
  6. package/dist/bugsnag/tool/error/update-error.js +256 -0
  7. package/dist/bugsnag/tool/event/get-event-details-from-dashboard-url.js +55 -0
  8. package/dist/bugsnag/tool/event/get-event.js +38 -0
  9. package/dist/bugsnag/tool/event/list-error-events.js +62 -0
  10. package/dist/bugsnag/tool/performance/get-network-endpoint-groupings.js +46 -0
  11. package/dist/bugsnag/tool/performance/get-span-group.js +73 -0
  12. package/dist/bugsnag/tool/performance/get-trace.js +83 -0
  13. package/dist/bugsnag/tool/performance/list-span-groups.js +111 -0
  14. package/dist/bugsnag/tool/performance/list-spans.js +97 -0
  15. package/dist/bugsnag/tool/performance/list-trace-fields.js +41 -0
  16. package/dist/bugsnag/tool/performance/set-network-endpoint-groupings.js +90 -0
  17. package/dist/bugsnag/tool/project/get-current-project.js +31 -0
  18. package/dist/bugsnag/tool/project/list-project-event-filters.js +42 -0
  19. package/dist/bugsnag/tool/project/list-projects.js +43 -0
  20. package/dist/bugsnag/tool/release/get-build.js +50 -0
  21. package/dist/bugsnag/tool/release/get-release.js +68 -0
  22. package/dist/bugsnag/tool/release/list-releases.js +88 -0
  23. package/dist/common/prompts.js +9 -0
  24. package/dist/common/server.js +16 -0
  25. package/dist/common/transport-http.js +2 -0
  26. package/dist/package.json.js +1 -1
  27. package/dist/reflect/client.js +66 -1
  28. package/dist/reflect/config/constants.js +3 -1
  29. package/dist/reflect/prompt/sap-test.js +29 -0
  30. package/dist/reflect/tool/recording/add-prompt-step.js +60 -0
  31. package/dist/reflect/tool/recording/add-segment.js +56 -0
  32. package/dist/reflect/tool/recording/connect-to-session.js +89 -0
  33. package/dist/reflect/tool/recording/delete-previous-step.js +47 -0
  34. package/dist/reflect/tool/recording/get-screenshot.js +55 -0
  35. package/dist/reflect/tool/tests/list-segments.js +68 -0
  36. package/dist/reflect/websocket-manager.js +92 -0
  37. package/dist/zephyr/client.js +2 -0
  38. package/dist/zephyr/common/rest-api-schemas.js +173 -312
  39. package/dist/zephyr/tool/issue-link/get-test-executions.js +1 -1
  40. package/dist/zephyr/tool/test-case/create-issue-link.js +4 -2
  41. package/dist/zephyr/tool/test-case/create-test-script.js +4 -2
  42. package/dist/zephyr/tool/test-case/create-test-steps.js +4 -2
  43. package/dist/zephyr/tool/test-case/create-web-link.js +5 -2
  44. package/dist/zephyr/tool/test-case/get-test-steps.js +4 -2
  45. package/dist/zephyr/tool/test-case/update-test-case.js +2 -0
  46. package/dist/zephyr/tool/test-cycle/create-issue-link.js +4 -2
  47. package/dist/zephyr/tool/test-cycle/create-web-link.js +4 -2
  48. package/dist/zephyr/tool/test-cycle/update-test-cycle.js +1 -0
  49. package/dist/zephyr/tool/test-execution/create-issue-link.js +4 -2
  50. package/dist/zephyr/tool/test-execution/get-test-steps.js +4 -2
  51. package/dist/zephyr/tool/test-execution/update-test-execution.js +5 -2
  52. package/dist/zephyr/tool/test-execution/update-test-steps.js +89 -0
  53. package/package.json +11 -9
@@ -0,0 +1,50 @@
1
+ import { z } from "zod";
2
+ import { Tool, ToolError } from "../../../common/tools.js";
3
+ import { toolInputParameters } from "../../input-schemas.js";
4
+ const inputSchema = z.object({
5
+ projectId: toolInputParameters.projectId,
6
+ buildId: toolInputParameters.buildId
7
+ });
8
+ class GetBuild extends Tool {
9
+ specification = {
10
+ title: "Get Build",
11
+ summary: "Get more details for a specific build by its ID",
12
+ purpose: "Retrieve detailed information about a build for analysis and debugging",
13
+ useCases: [
14
+ "View build metadata such as version, source control info, and error counts",
15
+ "Analyze a specific build to correlate with error spikes or deployments",
16
+ "See the stability targets for a project and if the build meets them"
17
+ ],
18
+ inputSchema,
19
+ examples: [
20
+ {
21
+ description: "Get details for a specific build",
22
+ parameters: {
23
+ buildId: "5f8d0d55c9e77c0017a1b2c3"
24
+ },
25
+ expectedOutput: "JSON object with build details including version, source control info, error counts and stability data."
26
+ }
27
+ ],
28
+ hints: ["Build IDs can be found using the List builds tool"],
29
+ readOnly: true,
30
+ idempotent: true,
31
+ outputDescription: "JSON object containing build details along with stability metrics such as user and session stability, and whether it meets project targets"
32
+ };
33
+ handle = async (args, _extra) => {
34
+ const params = inputSchema.parse(args);
35
+ const project = await this.client.getInputProject(params.projectId);
36
+ const response = await this.client.projectApi.getProjectReleaseById(
37
+ project.id,
38
+ params.buildId
39
+ );
40
+ if (!response.body)
41
+ throw new ToolError(`No build for ${params.buildId} found.`);
42
+ const build = this.client.addStabilityData(response.body, project);
43
+ return {
44
+ content: [{ type: "text", text: JSON.stringify(build) }]
45
+ };
46
+ };
47
+ }
48
+ export {
49
+ GetBuild
50
+ };
@@ -0,0 +1,68 @@
1
+ import { z } from "zod";
2
+ import { Tool, ToolError } from "../../../common/tools.js";
3
+ import { toolInputParameters } from "../../input-schemas.js";
4
+ const inputSchema = z.object({
5
+ projectId: toolInputParameters.projectId,
6
+ releaseId: toolInputParameters.releaseId
7
+ });
8
+ class GetRelease extends Tool {
9
+ specification = {
10
+ title: "Get Release",
11
+ summary: "Get more details for a specific release by its ID, including source control information and associated builds",
12
+ purpose: "Retrieve detailed information about a release for analysis and debugging",
13
+ useCases: [
14
+ "View release metadata such as version, source control info, and error counts",
15
+ "Analyze the stability data and targets for a release",
16
+ "See the builds that make up the release"
17
+ ],
18
+ inputSchema,
19
+ examples: [
20
+ {
21
+ description: "Get details for a specific release",
22
+ parameters: {
23
+ releaseId: "5f8d0d55c9e77c0017a1b2c3"
24
+ },
25
+ expectedOutput: "JSON object with release details including version, source control info, error counts and stability data."
26
+ }
27
+ ],
28
+ hints: ["Release IDs can be found using the List releases tool"],
29
+ readOnly: true,
30
+ idempotent: true,
31
+ outputDescription: "JSON object containing release details along with stability metrics such as user and session stability, and whether it meets project targets"
32
+ };
33
+ handle = async (args, _extra) => {
34
+ const params = inputSchema.parse(args);
35
+ const project = await this.client.getInputProject(params.projectId);
36
+ const releaseResponse = await this.client.projectApi.getReleaseGroup(
37
+ params.releaseId
38
+ );
39
+ if (!releaseResponse.body)
40
+ throw new ToolError(`No release for ${params.releaseId} found.`);
41
+ const release = this.client.addStabilityData(releaseResponse.body, project);
42
+ let builds = [];
43
+ if (releaseResponse.body) {
44
+ const buildsResponse = await this.client.projectApi.listBuildsInRelease(
45
+ params.releaseId
46
+ );
47
+ if (buildsResponse.body) {
48
+ builds = buildsResponse.body.map(
49
+ (b) => this.client.addStabilityData(b, project)
50
+ );
51
+ }
52
+ }
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: JSON.stringify({
58
+ release,
59
+ builds
60
+ })
61
+ }
62
+ ]
63
+ };
64
+ };
65
+ }
66
+ export {
67
+ GetRelease
68
+ };
@@ -0,0 +1,88 @@
1
+ import { z } from "zod";
2
+ import { Tool } from "../../../common/tools.js";
3
+ import { toolInputParameters } from "../../input-schemas.js";
4
+ const inputSchema = z.object({
5
+ projectId: toolInputParameters.projectId,
6
+ releaseStage: toolInputParameters.releaseStage,
7
+ visibleOnly: z.boolean().describe(
8
+ "Whether to only include releases that are marked as visible in the dashboard"
9
+ ).default(false),
10
+ perPage: toolInputParameters.perPage,
11
+ nextUrl: toolInputParameters.nextUrl
12
+ });
13
+ class ListReleases extends Tool {
14
+ specification = {
15
+ title: "List Releases",
16
+ summary: "List releases for a project",
17
+ purpose: "Retrieve a list of release summaries to analyze deployment history and associated errors",
18
+ useCases: [
19
+ "View recent releases to correlate with error spikes",
20
+ "Filter releases by stage (e.g. production, staging) for targeted analysis"
21
+ ],
22
+ inputSchema,
23
+ examples: [
24
+ {
25
+ description: "List production releases for a project",
26
+ parameters: {},
27
+ expectedOutput: "JSON array of release objects in the production stage"
28
+ },
29
+ {
30
+ description: "List staging releases for a project",
31
+ parameters: {
32
+ releaseStage: "staging"
33
+ },
34
+ expectedOutput: "JSON array of release objects in the staging stage"
35
+ },
36
+ {
37
+ description: "Get the next page of results",
38
+ parameters: {
39
+ nextUrl: "/projects/515fb9337c1074f6fd000003/releases?offset=30&per_page=30"
40
+ },
41
+ expectedOutput: "JSON array of release objects with metadata from the next page"
42
+ }
43
+ ],
44
+ hints: [
45
+ "Use the Get Release tool to get more details on a specific release, including the builds it contains",
46
+ "The release stage defaults to 'production' if not specified",
47
+ "Use visibleOnly to filter out releases that have been marked as hidden in the dashboard"
48
+ ],
49
+ readOnly: true,
50
+ idempotent: true,
51
+ outputDescription: "JSON array of release summary objects with metadata, with a URL to the next page if more results are available"
52
+ };
53
+ handle = async (args, _extra) => {
54
+ const params = inputSchema.parse(args);
55
+ const project = await this.client.getInputProject(params.projectId);
56
+ const response = await this.client.projectApi.listProjectReleaseGroups(
57
+ project.id,
58
+ params.releaseStage,
59
+ false,
60
+ // Not top-only
61
+ params.visibleOnly,
62
+ params.perPage,
63
+ params.nextUrl
64
+ );
65
+ let releases = [];
66
+ if (response.body) {
67
+ releases = response.body.map(
68
+ (r) => this.client.addStabilityData(r, project)
69
+ );
70
+ }
71
+ return {
72
+ content: [
73
+ {
74
+ type: "text",
75
+ text: JSON.stringify({
76
+ data: releases,
77
+ next_url: response.nextUrl ?? void 0,
78
+ data_count: releases.length,
79
+ total_count: response.totalCount ?? void 0
80
+ })
81
+ }
82
+ ]
83
+ };
84
+ };
85
+ }
86
+ export {
87
+ ListReleases
88
+ };
@@ -0,0 +1,9 @@
1
+ class Prompt {
2
+ client;
3
+ constructor(client) {
4
+ this.client = client;
5
+ }
6
+ }
7
+ export {
8
+ Prompt
9
+ };
@@ -10,6 +10,7 @@ class SmartBearMcpServer extends McpServer {
10
10
  cache;
11
11
  samplingSupported = false;
12
12
  elicitationSupported = false;
13
+ clients = [];
13
14
  constructor() {
14
15
  super(
15
16
  {
@@ -17,6 +18,15 @@ class SmartBearMcpServer extends McpServer {
17
18
  version: MCP_SERVER_VERSION
18
19
  },
19
20
  {
21
+ instructions: `When creating or editing a Reflect test using a connected recording session, follow these guidelines:
22
+
23
+ 1. After connecting to a session, get the list of segments for the session's platform type so you know what actions could be added via segments vs needing to create new steps. Do not list tests, only list segments.
24
+ 2. Before performing an action, take a screenshot to understand the current state of the application.
25
+ 3. Each add_prompt_step request should perform a single action or assertion. Do not combine multiple actions or assertions into a single step.
26
+ 4. Only perform one action at a time unless you're sure the action won't move the application to a different screen. For example, you can send multiple add_prompt_step requests to fill out individual form fields if those fields are visible on the current screen.
27
+ 5. Check the list of existing Segments to see if a Segment exists that achieves a similar goal to what you're trying to do next. If so, add the segment instead of creating new steps.
28
+ 6. If a step fails, use delete_previous_step to remove it and try a different approach.
29
+ 7. After completing a task, if the task required multiple prompt steps, add a final prompt step that validates the current state of the page based on what you see on the screen. In your validation, do not reference information that can change from run to run.`,
20
30
  capabilities: {
21
31
  resources: { listChanged: true },
22
32
  // Server supports dynamic resource lists
@@ -46,7 +56,13 @@ class SmartBearMcpServer extends McpServer {
46
56
  isElicitationSupported() {
47
57
  return this.elicitationSupported;
48
58
  }
59
+ async cleanupSession(mcpSessionId) {
60
+ for (const client of this.clients) {
61
+ await client.cleanupSession?.(mcpSessionId);
62
+ }
63
+ }
49
64
  async addClient(client) {
65
+ this.clients.push(client);
50
66
  await client.registerTools(
51
67
  (params, cb) => {
52
68
  const toolName = `${client.toolPrefix}_${params.title.replace(/\s+/g, "_").toLowerCase()}`;
@@ -153,6 +153,7 @@ async function createNewTransport(req, res, transports) {
153
153
  if (transport.sessionId) {
154
154
  console.log(`[MCP] Session closed: ${transport.sessionId}`);
155
155
  transports.delete(transport.sessionId);
156
+ server.cleanupSession(transport.sessionId);
156
157
  }
157
158
  };
158
159
  await server.connect(transport);
@@ -205,6 +206,7 @@ async function handleLegacySseRequest(req, res, transports) {
205
206
  transports.set(transport.sessionId, { server, transport });
206
207
  res.on("close", () => {
207
208
  transports.delete(transport.sessionId);
209
+ server.cleanupSession(transport.sessionId);
208
210
  });
209
211
  await server.connect(transport);
210
212
  }
@@ -1,4 +1,4 @@
1
- const version = "0.15.0";
1
+ const version = "0.17.0";
2
2
  const config = { "mcpServerName": "SmartBear MCP Server" };
3
3
  const packageJson = {
4
4
  version,
@@ -1,12 +1,20 @@
1
1
  import { z } from "zod";
2
2
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
3
+ import { ToolError } from "../common/tools.js";
3
4
  import { API_KEY_HEADER } from "./config/constants.js";
5
+ import { SapTest } from "./prompt/sap-test.js";
6
+ import { AddPromptStep } from "./tool/recording/add-prompt-step.js";
7
+ import { AddSegment } from "./tool/recording/add-segment.js";
8
+ import { ConnectToSession } from "./tool/recording/connect-to-session.js";
9
+ import { DeletePreviousStep } from "./tool/recording/delete-previous-step.js";
10
+ import { GetScreenshot } from "./tool/recording/get-screenshot.js";
4
11
  import { CancelSuiteExecution } from "./tool/suites/cancel-suite-execution.js";
5
12
  import { ExecuteSuite } from "./tool/suites/execute-suite.js";
6
13
  import { GetSuiteExecutionStatus } from "./tool/suites/get-suite-execution-status.js";
7
14
  import { ListSuiteExecutions } from "./tool/suites/list-suite-executions.js";
8
15
  import { ListSuites } from "./tool/suites/list-suites.js";
9
16
  import { GetTestStatus } from "./tool/tests/get-test-status.js";
17
+ import { ListSegments } from "./tool/tests/list-segments.js";
10
18
  import { ListTests } from "./tool/tests/list-tests.js";
11
19
  import { RunTest } from "./tool/tests/run-test.js";
12
20
  const ConfigurationSchema = z.object({
@@ -14,11 +22,16 @@ const ConfigurationSchema = z.object({
14
22
  });
15
23
  class ReflectClient {
16
24
  headers = {};
25
+ apiToken = "";
26
+ activeConnections = /* @__PURE__ */ new Map();
27
+ sessionStates = /* @__PURE__ */ new Map();
28
+ mcpSessionConnections = /* @__PURE__ */ new Map();
17
29
  name = "Reflect";
18
30
  toolPrefix = "reflect";
19
31
  configPrefix = "Reflect";
20
32
  config = ConfigurationSchema;
21
33
  async configure(_server, config, _cache) {
34
+ this.apiToken = config.api_token;
22
35
  this.headers = {
23
36
  [API_KEY_HEADER]: `${config.api_token}`,
24
37
  "Content-Type": "application/json",
@@ -28,9 +41,49 @@ class ReflectClient {
28
41
  isConfigured() {
29
42
  return Object.keys(this.headers).length !== 0;
30
43
  }
44
+ getApiToken() {
45
+ return this.apiToken;
46
+ }
31
47
  getHeaders() {
32
48
  return this.headers;
33
49
  }
50
+ getSessionState(sessionId) {
51
+ return this.sessionStates.get(sessionId);
52
+ }
53
+ isSessionConnected(sessionId) {
54
+ const wsManager = this.activeConnections.get(sessionId);
55
+ return wsManager?.isConnected() ?? false;
56
+ }
57
+ getConnectedSession(sessionId) {
58
+ if (!this.isSessionConnected(sessionId)) {
59
+ throw new ToolError(
60
+ `Session ${sessionId} is not connected. Call connect_to_session first.`
61
+ );
62
+ }
63
+ return this.activeConnections.get(sessionId);
64
+ }
65
+ registerConnection(sessionId, ws, state, mcpSessionId) {
66
+ this.activeConnections.set(sessionId, ws);
67
+ this.sessionStates.set(sessionId, state);
68
+ if (mcpSessionId) {
69
+ const existing = this.mcpSessionConnections.get(mcpSessionId) ?? /* @__PURE__ */ new Set();
70
+ existing.add(sessionId);
71
+ this.mcpSessionConnections.set(mcpSessionId, existing);
72
+ }
73
+ }
74
+ async cleanupSession(mcpSessionId) {
75
+ const reflectSessionIds = this.mcpSessionConnections.get(mcpSessionId);
76
+ if (!reflectSessionIds) return;
77
+ for (const reflectSessionId of reflectSessionIds) {
78
+ const ws = this.activeConnections.get(reflectSessionId);
79
+ if (ws) {
80
+ await ws.disconnect();
81
+ this.activeConnections.delete(reflectSessionId);
82
+ this.sessionStates.delete(reflectSessionId);
83
+ }
84
+ }
85
+ this.mcpSessionConnections.delete(mcpSessionId);
86
+ }
34
87
  async registerTools(register, _getInput) {
35
88
  const tools = [
36
89
  new ListSuites(this),
@@ -40,12 +93,24 @@ class ReflectClient {
40
93
  new CancelSuiteExecution(this),
41
94
  new ListTests(this),
42
95
  new RunTest(this),
43
- new GetTestStatus(this)
96
+ new GetTestStatus(this),
97
+ new ListSegments(this),
98
+ new ConnectToSession(this),
99
+ new AddPromptStep(this),
100
+ new GetScreenshot(this),
101
+ new DeletePreviousStep(this),
102
+ new AddSegment(this)
44
103
  ];
45
104
  for (const tool of tools) {
46
105
  register(tool.specification, tool.handle);
47
106
  }
48
107
  }
108
+ registerPrompts(register) {
109
+ const prompts = [new SapTest(this)];
110
+ for (const prompt of prompts) {
111
+ register(prompt.name, prompt.params, prompt.callback);
112
+ }
113
+ }
49
114
  }
50
115
  export {
51
116
  ReflectClient
@@ -1,6 +1,8 @@
1
1
  const API_KEY_HEADER = "X-API-KEY";
2
2
  const API_HOSTNAME = "api.reflect.run";
3
+ const WEBSOCKET_HOSTNAME = "recording.us-east-1.reflect.run";
3
4
  export {
4
5
  API_HOSTNAME,
5
- API_KEY_HEADER
6
+ API_KEY_HEADER,
7
+ WEBSOCKET_HOSTNAME
6
8
  };
@@ -0,0 +1,29 @@
1
+ import { Prompt } from "../../common/prompts.js";
2
+ class SapTest extends Prompt {
3
+ name = "reflect-sap-test";
4
+ params = {
5
+ title: "Reflect SAP Test",
6
+ description: "Guidelines for creating a Reflect test against an SAP S4/HANA or SAP BTP application.",
7
+ argsSchema: {}
8
+ };
9
+ callback = () => ({
10
+ messages: [
11
+ {
12
+ role: "user",
13
+ content: {
14
+ type: "text",
15
+ text: `When creating an SAP test:
16
+ 1. Always precede an input step with a click step.
17
+ 2. Always capture a screenshot after each step, even form field actions.
18
+ 3. If text is ellipsized with a hyphen, do not include the hyphen in your prompt step.
19
+ 4. If you navigate to the wrong page, use browser navigation (e.g. "Click the back button") or on-screen navigation to get to the prior screen. Make sure to delete the original step and any steps you added to navigate back to the previous screen so that the test is repeatable and contains no unnecessary steps.
20
+ 5. When applicable, use the prompt "Press the tab key" to tab through fields and the prompt "Press the enter key" to submit a form.
21
+ 6. When building tests from BPD docs, if the input value for the next step is based on an example value in the doc, prompt the user to ask for their desired value. Provide the example value in your prompt so the user has additional context.`
22
+ }
23
+ }
24
+ ]
25
+ });
26
+ }
27
+ export {
28
+ SapTest
29
+ };
@@ -0,0 +1,60 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { Tool, ToolError } from "../../../common/tools.js";
4
+ class AddPromptStep extends Tool {
5
+ specification = {
6
+ title: "Add Prompt Step",
7
+ summary: "Add a natural language prompt step to an active Reflect recording session",
8
+ readOnly: false,
9
+ idempotent: false,
10
+ parameters: [
11
+ {
12
+ name: "sessionId",
13
+ type: z.string(),
14
+ description: "The ID of the Reflect recording session",
15
+ required: true
16
+ },
17
+ {
18
+ name: "prompt",
19
+ type: z.string(),
20
+ description: 'The natural language prompt describing the test step. The prompt should describe a single action, assertion, or query. The prompt can only contain literal text; it cannot contain template variables, secrets, or other dynamic syntax. If we are in a Web recording, the prompt can perform browser navigation (e.g. "Click on the back button", "Navigate to https://www.example.com") and use the tab and enter keys to navigate (e.g. "Press the tab key", "Press the enter key").',
21
+ required: true
22
+ }
23
+ ]
24
+ };
25
+ handle = async (args) => {
26
+ const { sessionId, prompt } = args;
27
+ if (!sessionId) throw new ToolError("sessionId argument is required");
28
+ if (!prompt) throw new ToolError("prompt argument is required");
29
+ const wsManager = this.client.getConnectedSession(sessionId);
30
+ const id = randomUUID();
31
+ const responsePromise = wsManager.waitForResponse(id);
32
+ await wsManager.sendMcpMessage({
33
+ type: "mcp:add-prompt-step",
34
+ id,
35
+ prompt
36
+ });
37
+ const response = await responsePromise;
38
+ const result = response.result;
39
+ return {
40
+ content: [
41
+ {
42
+ type: "text",
43
+ text: JSON.stringify({
44
+ success: true,
45
+ sessionId,
46
+ message: `Successfully added prompt step: "${prompt}"`,
47
+ intent: {
48
+ prompt,
49
+ type: result?.type,
50
+ response: result?.response
51
+ }
52
+ })
53
+ }
54
+ ]
55
+ };
56
+ };
57
+ }
58
+ export {
59
+ AddPromptStep
60
+ };
@@ -0,0 +1,56 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { Tool, ToolError } from "../../../common/tools.js";
4
+ class AddSegment extends Tool {
5
+ specification = {
6
+ title: "Add Segment",
7
+ summary: "Insert a reusable test segment into an active Reflect recording session",
8
+ readOnly: false,
9
+ idempotent: false,
10
+ parameters: [
11
+ {
12
+ name: "sessionId",
13
+ type: z.string(),
14
+ description: "The ID of the Reflect recording session",
15
+ required: true
16
+ },
17
+ {
18
+ name: "segmentId",
19
+ type: z.number(),
20
+ description: "The ID of the segment to add",
21
+ required: true
22
+ }
23
+ ]
24
+ };
25
+ handle = async (args) => {
26
+ const { sessionId, segmentId } = args;
27
+ if (!sessionId) throw new ToolError("sessionId argument is required");
28
+ if (segmentId === void 0 || segmentId === null)
29
+ throw new ToolError("segmentId argument is required");
30
+ const wsManager = this.client.getConnectedSession(sessionId);
31
+ const id = randomUUID();
32
+ const responsePromise = wsManager.waitForResponse(id);
33
+ await wsManager.sendMcpMessage({
34
+ type: "mcp:add-segment",
35
+ id,
36
+ segmentId
37
+ });
38
+ await responsePromise;
39
+ return {
40
+ content: [
41
+ {
42
+ type: "text",
43
+ text: JSON.stringify({
44
+ success: true,
45
+ sessionId,
46
+ message: `Successfully added and executed segment ${segmentId}`,
47
+ segmentId
48
+ })
49
+ }
50
+ ]
51
+ };
52
+ };
53
+ }
54
+ export {
55
+ AddSegment
56
+ };
@@ -0,0 +1,89 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { Tool, ToolError } from "../../../common/tools.js";
4
+ import { WebSocketManager } from "../../websocket-manager.js";
5
+ class ConnectToSession extends Tool {
6
+ specification = {
7
+ title: "Connect To Session",
8
+ summary: `Connect to an active Reflect recording session via WebSocket to enable interactive control. When creating or editing a Reflect test using a connected recording session, follow these guidelines:
9
+
10
+ 1. After connecting to a session, get the list of segments for the session's platform type so you know what actions could be added via segments vs needing to create new steps. Do not list tests, only list segments.
11
+ 2. Before performing an action, take a screenshot to understand the current state of the application.
12
+ 3. Each add_prompt_step request should perform a single action or assertion. Do not combine multiple actions or assertions into a single step.
13
+ 4. Only perform one action at a time unless you're sure the action won't move the application to a different screen. For example, you can send multiple add_prompt_step requests to fill out individual form fields if those fields are visible on the current screen.
14
+ 5. Check the list of existing Segments to see if a Segment exists that achieves a similar goal to what you're trying to do next. If so, add the segment instead of creating new steps.
15
+ 6. If a step fails, use delete_previous_step to remove it and try a different approach.
16
+ 7. After completing a task, if the task required multiple prompt steps, add a final prompt step that validates the current state of the page based on what you see on the screen. In your validation, do not reference information that can change from run to run.`,
17
+ readOnly: false,
18
+ idempotent: true,
19
+ parameters: [
20
+ {
21
+ name: "sessionId",
22
+ type: z.string(),
23
+ description: "The ID of the Reflect recording session to connect to",
24
+ required: true
25
+ }
26
+ ]
27
+ };
28
+ handle = async (args, extra) => {
29
+ const { sessionId } = args;
30
+ if (!sessionId) throw new ToolError("sessionId argument is required");
31
+ if (this.client.isSessionConnected(sessionId)) {
32
+ const state = this.client.getSessionState(sessionId);
33
+ const { platform: platform2, test: test2 } = state;
34
+ return {
35
+ content: [
36
+ {
37
+ type: "text",
38
+ text: JSON.stringify({ success: true, sessionId, platform: platform2, test: test2 })
39
+ }
40
+ ]
41
+ };
42
+ }
43
+ const wsManager = new WebSocketManager(
44
+ sessionId,
45
+ this.client.getApiToken()
46
+ );
47
+ try {
48
+ await wsManager.connect();
49
+ } catch (error) {
50
+ throw new ToolError(
51
+ `Failed to connect to session: ${error.message}`
52
+ );
53
+ }
54
+ const connectId = randomUUID();
55
+ let connectResponse;
56
+ try {
57
+ const connectResponsePromise = wsManager.waitForResponse(connectId);
58
+ await wsManager.sendMcpMessage({
59
+ type: "mcp:connect-to-session",
60
+ id: connectId
61
+ });
62
+ connectResponse = await connectResponsePromise;
63
+ } catch (connectError) {
64
+ await wsManager.disconnect();
65
+ throw new ToolError(
66
+ `MCP connect-to-session failed: ${connectError instanceof Error ? connectError.message : String(connectError)}`
67
+ );
68
+ }
69
+ const mcpSessionId = extra?.sessionId;
70
+ const { platform, test } = connectResponse;
71
+ this.client.registerConnection(
72
+ sessionId,
73
+ wsManager,
74
+ { platform, test },
75
+ mcpSessionId
76
+ );
77
+ return {
78
+ content: [
79
+ {
80
+ type: "text",
81
+ text: JSON.stringify({ success: true, sessionId, platform, test })
82
+ }
83
+ ]
84
+ };
85
+ };
86
+ }
87
+ export {
88
+ ConnectToSession
89
+ };