@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.
- package/dist/bugsnag/client/api/Error.js +43 -0
- package/dist/bugsnag/client.js +44 -1310
- 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/event/list-error-events.js +62 -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/reflect/client.js +66 -1
- package/dist/reflect/config/constants.js +3 -1
- 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/tests/list-segments.js +68 -0
- package/dist/reflect/websocket-manager.js +92 -0
- package/dist/zephyr/client.js +2 -0
- package/dist/zephyr/common/rest-api-schemas.js +173 -312
- package/dist/zephyr/tool/issue-link/get-test-executions.js +1 -1
- package/dist/zephyr/tool/test-case/create-issue-link.js +4 -2
- package/dist/zephyr/tool/test-case/create-test-script.js +4 -2
- package/dist/zephyr/tool/test-case/create-test-steps.js +4 -2
- package/dist/zephyr/tool/test-case/create-web-link.js +5 -2
- package/dist/zephyr/tool/test-case/get-test-steps.js +4 -2
- package/dist/zephyr/tool/test-case/update-test-case.js +2 -0
- package/dist/zephyr/tool/test-cycle/create-issue-link.js +4 -2
- package/dist/zephyr/tool/test-cycle/create-web-link.js +4 -2
- package/dist/zephyr/tool/test-cycle/update-test-cycle.js +1 -0
- package/dist/zephyr/tool/test-execution/create-issue-link.js +4 -2
- package/dist/zephyr/tool/test-execution/get-test-steps.js +4 -2
- package/dist/zephyr/tool/test-execution/update-test-execution.js +5 -2
- package/dist/zephyr/tool/test-execution/update-test-steps.js +89 -0
- 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
|
+
};
|
package/dist/common/server.js
CHANGED
|
@@ -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
|
}
|
package/dist/package.json.js
CHANGED
package/dist/reflect/client.js
CHANGED
|
@@ -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
|
|
@@ -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
|
+
};
|