@smartbear/mcp 0.14.1 → 0.16.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 (68) hide show
  1. package/README.md +1 -1
  2. package/dist/bugsnag/client.js +47 -1340
  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/performance/get-network-endpoint-groupings.js +46 -0
  10. package/dist/bugsnag/tool/performance/get-span-group.js +73 -0
  11. package/dist/bugsnag/tool/performance/get-trace.js +83 -0
  12. package/dist/bugsnag/tool/performance/list-span-groups.js +111 -0
  13. package/dist/bugsnag/tool/performance/list-spans.js +97 -0
  14. package/dist/bugsnag/tool/performance/list-trace-fields.js +41 -0
  15. package/dist/bugsnag/tool/performance/set-network-endpoint-groupings.js +90 -0
  16. package/dist/bugsnag/tool/project/get-current-project.js +31 -0
  17. package/dist/bugsnag/tool/project/list-project-event-filters.js +42 -0
  18. package/dist/bugsnag/tool/project/list-projects.js +43 -0
  19. package/dist/bugsnag/tool/release/get-build.js +50 -0
  20. package/dist/bugsnag/tool/release/get-release.js +68 -0
  21. package/dist/bugsnag/tool/release/list-releases.js +88 -0
  22. package/dist/common/prompts.js +9 -0
  23. package/dist/common/server.js +16 -0
  24. package/dist/common/transport-http.js +2 -0
  25. package/dist/package.json.js +1 -1
  26. package/dist/qmetry/client/api/client-api.js +2 -1
  27. package/dist/qmetry/client/automation.js +2 -1
  28. package/dist/qmetry/client/tools/testcase-tools.js +269 -1
  29. package/dist/qmetry/client/tools/testsuite-tools.js +1 -1
  30. package/dist/reflect/client.js +82 -255
  31. package/dist/reflect/config/constants.js +8 -0
  32. package/dist/reflect/prompt/sap-test.js +29 -0
  33. package/dist/reflect/tool/recording/add-prompt-step.js +60 -0
  34. package/dist/reflect/tool/recording/add-segment.js +56 -0
  35. package/dist/reflect/tool/recording/connect-to-session.js +89 -0
  36. package/dist/reflect/tool/recording/delete-previous-step.js +47 -0
  37. package/dist/reflect/tool/recording/get-screenshot.js +55 -0
  38. package/dist/reflect/tool/suites/cancel-suite-execution.js +50 -0
  39. package/dist/reflect/tool/suites/execute-suite.js +40 -0
  40. package/dist/reflect/tool/suites/get-suite-execution-status.js +50 -0
  41. package/dist/reflect/tool/suites/list-suite-executions.js +40 -0
  42. package/dist/reflect/tool/suites/list-suites.js +27 -0
  43. package/dist/reflect/tool/tests/get-test-status.js +42 -0
  44. package/dist/reflect/tool/tests/list-segments.js +68 -0
  45. package/dist/reflect/tool/tests/list-tests.js +27 -0
  46. package/dist/reflect/tool/tests/run-test.js +40 -0
  47. package/dist/reflect/websocket-manager.js +92 -0
  48. package/dist/zephyr/client.js +35 -1
  49. package/dist/zephyr/common/rest-api-schemas.js +463 -477
  50. package/dist/zephyr/tool/folder/create-folder.js +55 -0
  51. package/dist/zephyr/tool/issue-link/get-test-cases.js +33 -0
  52. package/dist/zephyr/tool/issue-link/get-test-cycles.js +32 -0
  53. package/dist/zephyr/tool/issue-link/get-test-executions.js +32 -0
  54. package/dist/zephyr/tool/test-case/create-issue-link.js +38 -0
  55. package/dist/zephyr/tool/test-case/create-test-script.js +57 -0
  56. package/dist/zephyr/tool/test-case/create-test-steps.js +91 -0
  57. package/dist/zephyr/tool/test-case/create-web-link.js +5 -2
  58. package/dist/zephyr/tool/test-case/get-links.js +32 -0
  59. package/dist/zephyr/tool/test-case/get-test-script.js +46 -0
  60. package/dist/zephyr/tool/test-case/get-test-steps.js +56 -0
  61. package/dist/zephyr/tool/test-cycle/create-issue-link.js +45 -0
  62. package/dist/zephyr/tool/test-cycle/create-web-link.js +56 -0
  63. package/dist/zephyr/tool/test-cycle/get-links.js +39 -0
  64. package/dist/zephyr/tool/test-execution/create-issue-link.js +45 -0
  65. package/dist/zephyr/tool/test-execution/get-test-execution-links.js +39 -0
  66. package/dist/zephyr/tool/test-execution/get-test-steps.js +75 -0
  67. package/dist/zephyr/tool/test-execution/update-test-execution.js +73 -0
  68. package/package.json +11 -9
@@ -1,18 +1,39 @@
1
1
  import { z } from "zod";
2
2
  import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "../common/info.js";
3
3
  import { ToolError } from "../common/tools.js";
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";
11
+ import { CancelSuiteExecution } from "./tool/suites/cancel-suite-execution.js";
12
+ import { ExecuteSuite } from "./tool/suites/execute-suite.js";
13
+ import { GetSuiteExecutionStatus } from "./tool/suites/get-suite-execution-status.js";
14
+ import { ListSuiteExecutions } from "./tool/suites/list-suite-executions.js";
15
+ import { ListSuites } from "./tool/suites/list-suites.js";
16
+ import { GetTestStatus } from "./tool/tests/get-test-status.js";
17
+ import { ListSegments } from "./tool/tests/list-segments.js";
18
+ import { ListTests } from "./tool/tests/list-tests.js";
19
+ import { RunTest } from "./tool/tests/run-test.js";
4
20
  const ConfigurationSchema = z.object({
5
21
  api_token: z.string().describe("Reflect API authentication token")
6
22
  });
7
23
  class ReflectClient {
8
24
  headers = {};
25
+ apiToken = "";
26
+ activeConnections = /* @__PURE__ */ new Map();
27
+ sessionStates = /* @__PURE__ */ new Map();
28
+ mcpSessionConnections = /* @__PURE__ */ new Map();
9
29
  name = "Reflect";
10
30
  toolPrefix = "reflect";
11
31
  configPrefix = "Reflect";
12
32
  config = ConfigurationSchema;
13
33
  async configure(_server, config, _cache) {
34
+ this.apiToken = config.api_token;
14
35
  this.headers = {
15
- "X-API-KEY": `${config.api_token}`,
36
+ [API_KEY_HEADER]: `${config.api_token}`,
16
37
  "Content-Type": "application/json",
17
38
  "User-Agent": `${MCP_SERVER_NAME}/${MCP_SERVER_VERSION}`
18
39
  };
@@ -20,269 +41,75 @@ class ReflectClient {
20
41
  isConfigured() {
21
42
  return Object.keys(this.headers).length !== 0;
22
43
  }
23
- async listReflectSuites() {
24
- const response = await fetch("https://api.reflect.run/v1/suites", {
25
- method: "GET",
26
- headers: this.headers
27
- });
28
- return response.json();
44
+ getApiToken() {
45
+ return this.apiToken;
29
46
  }
30
- async listSuiteExecutions(suiteId) {
31
- const response = await fetch(
32
- `https://api.reflect.run/v1/suites/${suiteId}/executions`,
33
- {
34
- method: "GET",
35
- headers: this.headers
36
- }
37
- );
38
- return response.json();
39
- }
40
- async getSuiteExecutionStatus(suiteId, executionId) {
41
- const response = await fetch(
42
- `https://api.reflect.run/v1/suites/${suiteId}/executions/${executionId}`,
43
- {
44
- method: "GET",
45
- headers: this.headers
46
- }
47
- );
48
- return response.json();
47
+ getHeaders() {
48
+ return this.headers;
49
49
  }
50
- async executeSuite(suiteId) {
51
- const response = await fetch(
52
- `https://api.reflect.run/v1/suites/${suiteId}/executions`,
53
- {
54
- method: "POST",
55
- headers: this.headers
56
- }
57
- );
58
- return response.json();
50
+ getSessionState(sessionId) {
51
+ return this.sessionStates.get(sessionId);
59
52
  }
60
- async cancelSuiteExecution(suiteId, executionId) {
61
- const response = await fetch(
62
- `https://api.reflect.run/v1/suites/${suiteId}/executions/${executionId}/cancel`,
63
- {
64
- method: "PATCH",
65
- headers: this.headers
66
- }
67
- );
68
- return response.json();
53
+ isSessionConnected(sessionId) {
54
+ const wsManager = this.activeConnections.get(sessionId);
55
+ return wsManager?.isConnected() ?? false;
69
56
  }
70
- async listReflectTests() {
71
- const response = await fetch("https://api.reflect.run/v1/tests", {
72
- method: "GET",
73
- headers: this.headers
74
- });
75
- return response.json();
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);
76
64
  }
77
- async runReflectTest(testId) {
78
- const response = await fetch(
79
- `https://api.reflect.run/v1/tests/${testId}/executions`,
80
- {
81
- method: "POST",
82
- headers: this.headers
83
- }
84
- );
85
- return response.json();
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
+ }
86
73
  }
87
- async getReflectTestStatus(_testId, executionId) {
88
- const response = await fetch(
89
- `https://api.reflect.run/v1/executions/${executionId}`,
90
- {
91
- method: "GET",
92
- headers: this.headers
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);
93
83
  }
94
- );
95
- return response.json();
84
+ }
85
+ this.mcpSessionConnections.delete(mcpSessionId);
96
86
  }
97
87
  async registerTools(register, _getInput) {
98
- register(
99
- {
100
- title: "List Suites",
101
- summary: "Retrieve a list of all reflect suites available",
102
- parameters: []
103
- },
104
- async (_args, _extra) => {
105
- const response = await this.listReflectSuites();
106
- return {
107
- content: [{ type: "text", text: JSON.stringify(response) }]
108
- };
109
- }
110
- );
111
- register(
112
- {
113
- title: "List Suite Executions",
114
- summary: "List all executions for a given suite",
115
- parameters: [
116
- {
117
- name: "suiteId",
118
- type: z.string(),
119
- description: "ID of the reflect suite to list executions for",
120
- required: true
121
- }
122
- ]
123
- },
124
- async (args, _extra) => {
125
- if (!args.suiteId) throw new ToolError("suiteId argument is required");
126
- const response = await this.listSuiteExecutions(args.suiteId);
127
- return {
128
- content: [{ type: "text", text: JSON.stringify(response) }]
129
- };
130
- }
131
- );
132
- register(
133
- {
134
- title: "Get Suite Execution Status",
135
- summary: "Get the status of a reflect suite execution",
136
- parameters: [
137
- {
138
- name: "suiteId",
139
- type: z.string(),
140
- description: "ID of the reflect suite to get execution status for",
141
- required: true
142
- },
143
- {
144
- name: "executionId",
145
- type: z.string(),
146
- description: "ID of the reflect suite execution to get status for",
147
- required: true
148
- }
149
- ]
150
- },
151
- async (args, _extra) => {
152
- if (!args.suiteId || !args.executionId)
153
- throw new ToolError(
154
- "Both suiteId and executionId arguments are required"
155
- );
156
- const response = await this.getSuiteExecutionStatus(
157
- args.suiteId,
158
- args.executionId
159
- );
160
- return {
161
- content: [{ type: "text", text: JSON.stringify(response) }]
162
- };
163
- }
164
- );
165
- register(
166
- {
167
- title: "Execute Suite",
168
- summary: "Execute a reflect suite",
169
- parameters: [
170
- {
171
- name: "suiteId",
172
- type: z.string(),
173
- description: "ID of the reflect suite to list executions for",
174
- required: true
175
- }
176
- ]
177
- },
178
- async (args, _extra) => {
179
- if (!args.suiteId) throw new ToolError("suiteId argument is required");
180
- const response = await this.executeSuite(args.suiteId);
181
- return {
182
- content: [{ type: "text", text: JSON.stringify(response) }]
183
- };
184
- }
185
- );
186
- register(
187
- {
188
- title: "Cancel Suite Execution",
189
- summary: "Cancel a reflect suite execution",
190
- parameters: [
191
- {
192
- name: "suiteId",
193
- type: z.string(),
194
- description: "ID of the reflect suite to cancel execution for",
195
- required: true
196
- },
197
- {
198
- name: "executionId",
199
- type: z.string(),
200
- description: "ID of the reflect suite execution to cancel",
201
- required: true
202
- }
203
- ]
204
- },
205
- async (args, _extra) => {
206
- if (!args.suiteId || !args.executionId)
207
- throw new ToolError(
208
- "Both suiteId and executionId arguments are required"
209
- );
210
- const response = await this.cancelSuiteExecution(
211
- args.suiteId,
212
- args.executionId
213
- );
214
- return {
215
- content: [{ type: "text", text: JSON.stringify(response) }]
216
- };
217
- }
218
- );
219
- register(
220
- {
221
- title: "List Tests",
222
- summary: "List all reflect tests",
223
- parameters: []
224
- },
225
- async (_args, _extra) => {
226
- const response = await this.listReflectTests();
227
- return {
228
- content: [{ type: "text", text: JSON.stringify(response) }]
229
- };
230
- }
231
- );
232
- register(
233
- {
234
- title: "Run Test",
235
- summary: "Run a reflect test",
236
- parameters: [
237
- {
238
- name: "testId",
239
- type: z.string(),
240
- description: "ID of the reflect test to run",
241
- required: true
242
- }
243
- ]
244
- },
245
- async (args, _extra) => {
246
- if (!args.testId) throw new ToolError("testId argument is required");
247
- const response = await this.runReflectTest(args.testId);
248
- return {
249
- content: [{ type: "text", text: JSON.stringify(response) }]
250
- };
251
- }
252
- );
253
- register(
254
- {
255
- title: "Get Test Status",
256
- summary: "Get the status of a reflect test execution",
257
- parameters: [
258
- {
259
- name: "testId",
260
- type: z.string(),
261
- description: "ID of the reflect test to run",
262
- required: true
263
- },
264
- {
265
- name: "executionId",
266
- type: z.string(),
267
- description: "ID of the reflect test execution to get status for",
268
- required: true
269
- }
270
- ]
271
- },
272
- async (args, _extra) => {
273
- if (!args.testId || !args.executionId)
274
- throw new ToolError(
275
- "Both testId and executionId arguments are required"
276
- );
277
- const response = await this.getReflectTestStatus(
278
- args.testId,
279
- args.executionId
280
- );
281
- return {
282
- content: [{ type: "text", text: JSON.stringify(response) }]
283
- };
284
- }
285
- );
88
+ const tools = [
89
+ new ListSuites(this),
90
+ new ListSuiteExecutions(this),
91
+ new GetSuiteExecutionStatus(this),
92
+ new ExecuteSuite(this),
93
+ new CancelSuiteExecution(this),
94
+ new ListTests(this),
95
+ new RunTest(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)
103
+ ];
104
+ for (const tool of tools) {
105
+ register(tool.specification, tool.handle);
106
+ }
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
+ }
286
113
  }
287
114
  }
288
115
  export {
@@ -0,0 +1,8 @@
1
+ const API_KEY_HEADER = "X-API-KEY";
2
+ const API_HOSTNAME = "api.reflect.run";
3
+ const WEBSOCKET_HOSTNAME = "recording.us-east-1.reflect.run";
4
+ export {
5
+ API_HOSTNAME,
6
+ API_KEY_HEADER,
7
+ WEBSOCKET_HOSTNAME
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
+ };
@@ -0,0 +1,47 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { z } from "zod";
3
+ import { Tool, ToolError } from "../../../common/tools.js";
4
+ class DeletePreviousStep extends Tool {
5
+ specification = {
6
+ title: "Delete Previous Step",
7
+ summary: "Delete the last step added to an active Reflect recording session",
8
+ readOnly: false,
9
+ idempotent: false,
10
+ destructive: true,
11
+ parameters: [
12
+ {
13
+ name: "sessionId",
14
+ type: z.string(),
15
+ description: "The ID of the Reflect recording session",
16
+ required: true
17
+ }
18
+ ]
19
+ };
20
+ handle = async (args) => {
21
+ const { sessionId } = args;
22
+ if (!sessionId) throw new ToolError("sessionId argument is required");
23
+ const wsManager = this.client.getConnectedSession(sessionId);
24
+ const id = randomUUID();
25
+ const responsePromise = wsManager.waitForResponse(id);
26
+ await wsManager.sendMcpMessage({
27
+ type: "mcp:delete-step",
28
+ id
29
+ });
30
+ await responsePromise;
31
+ return {
32
+ content: [
33
+ {
34
+ type: "text",
35
+ text: JSON.stringify({
36
+ success: true,
37
+ sessionId,
38
+ message: "Successfully deleted previous step"
39
+ })
40
+ }
41
+ ]
42
+ };
43
+ };
44
+ }
45
+ export {
46
+ DeletePreviousStep
47
+ };