@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.
- package/README.md +1 -1
- package/dist/bugsnag/client.js +47 -1340
- package/dist/bugsnag/input-schemas.js +18 -18
- package/dist/bugsnag/tool/error/get-error.js +98 -0
- package/dist/bugsnag/tool/error/list-project-errors.js +102 -0
- package/dist/bugsnag/tool/error/update-error.js +256 -0
- package/dist/bugsnag/tool/event/get-event-details-from-dashboard-url.js +55 -0
- package/dist/bugsnag/tool/event/get-event.js +38 -0
- package/dist/bugsnag/tool/performance/get-network-endpoint-groupings.js +46 -0
- package/dist/bugsnag/tool/performance/get-span-group.js +73 -0
- package/dist/bugsnag/tool/performance/get-trace.js +83 -0
- package/dist/bugsnag/tool/performance/list-span-groups.js +111 -0
- package/dist/bugsnag/tool/performance/list-spans.js +97 -0
- package/dist/bugsnag/tool/performance/list-trace-fields.js +41 -0
- package/dist/bugsnag/tool/performance/set-network-endpoint-groupings.js +90 -0
- package/dist/bugsnag/tool/project/get-current-project.js +31 -0
- package/dist/bugsnag/tool/project/list-project-event-filters.js +42 -0
- package/dist/bugsnag/tool/project/list-projects.js +43 -0
- package/dist/bugsnag/tool/release/get-build.js +50 -0
- package/dist/bugsnag/tool/release/get-release.js +68 -0
- package/dist/bugsnag/tool/release/list-releases.js +88 -0
- package/dist/common/prompts.js +9 -0
- package/dist/common/server.js +16 -0
- package/dist/common/transport-http.js +2 -0
- package/dist/package.json.js +1 -1
- package/dist/qmetry/client/api/client-api.js +2 -1
- package/dist/qmetry/client/automation.js +2 -1
- package/dist/qmetry/client/tools/testcase-tools.js +269 -1
- package/dist/qmetry/client/tools/testsuite-tools.js +1 -1
- package/dist/reflect/client.js +82 -255
- package/dist/reflect/config/constants.js +8 -0
- package/dist/reflect/prompt/sap-test.js +29 -0
- package/dist/reflect/tool/recording/add-prompt-step.js +60 -0
- package/dist/reflect/tool/recording/add-segment.js +56 -0
- package/dist/reflect/tool/recording/connect-to-session.js +89 -0
- package/dist/reflect/tool/recording/delete-previous-step.js +47 -0
- package/dist/reflect/tool/recording/get-screenshot.js +55 -0
- package/dist/reflect/tool/suites/cancel-suite-execution.js +50 -0
- package/dist/reflect/tool/suites/execute-suite.js +40 -0
- package/dist/reflect/tool/suites/get-suite-execution-status.js +50 -0
- package/dist/reflect/tool/suites/list-suite-executions.js +40 -0
- package/dist/reflect/tool/suites/list-suites.js +27 -0
- package/dist/reflect/tool/tests/get-test-status.js +42 -0
- package/dist/reflect/tool/tests/list-segments.js +68 -0
- package/dist/reflect/tool/tests/list-tests.js +27 -0
- package/dist/reflect/tool/tests/run-test.js +40 -0
- package/dist/reflect/websocket-manager.js +92 -0
- package/dist/zephyr/client.js +35 -1
- package/dist/zephyr/common/rest-api-schemas.js +463 -477
- package/dist/zephyr/tool/folder/create-folder.js +55 -0
- package/dist/zephyr/tool/issue-link/get-test-cases.js +33 -0
- package/dist/zephyr/tool/issue-link/get-test-cycles.js +32 -0
- package/dist/zephyr/tool/issue-link/get-test-executions.js +32 -0
- package/dist/zephyr/tool/test-case/create-issue-link.js +38 -0
- package/dist/zephyr/tool/test-case/create-test-script.js +57 -0
- package/dist/zephyr/tool/test-case/create-test-steps.js +91 -0
- package/dist/zephyr/tool/test-case/create-web-link.js +5 -2
- package/dist/zephyr/tool/test-case/get-links.js +32 -0
- package/dist/zephyr/tool/test-case/get-test-script.js +46 -0
- package/dist/zephyr/tool/test-case/get-test-steps.js +56 -0
- package/dist/zephyr/tool/test-cycle/create-issue-link.js +45 -0
- package/dist/zephyr/tool/test-cycle/create-web-link.js +56 -0
- package/dist/zephyr/tool/test-cycle/get-links.js +39 -0
- package/dist/zephyr/tool/test-execution/create-issue-link.js +45 -0
- package/dist/zephyr/tool/test-execution/get-test-execution-links.js +39 -0
- package/dist/zephyr/tool/test-execution/get-test-steps.js +75 -0
- package/dist/zephyr/tool/test-execution/update-test-execution.js +73 -0
- package/package.json +11 -9
package/dist/reflect/client.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
24
|
-
|
|
25
|
-
method: "GET",
|
|
26
|
-
headers: this.headers
|
|
27
|
-
});
|
|
28
|
-
return response.json();
|
|
44
|
+
getApiToken() {
|
|
45
|
+
return this.apiToken;
|
|
29
46
|
}
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
51
|
-
|
|
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
|
-
|
|
61
|
-
const
|
|
62
|
-
|
|
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
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
84
|
+
}
|
|
85
|
+
this.mcpSessionConnections.delete(mcpSessionId);
|
|
96
86
|
}
|
|
97
87
|
async registerTools(register, _getInput) {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
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,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
|
+
};
|