@smartbear/mcp 0.19.2 → 0.20.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 +15 -2
- package/assets/icon.png +0 -0
- package/dist/bugsnag/client.js +19 -12
- package/dist/collaborator/client.js +10 -10
- package/dist/common/client-registry.js +2 -2
- package/dist/common/server.js +74 -111
- package/dist/common/shutdown.js +165 -0
- package/dist/common/transport-http.js +94 -12
- package/dist/common/transport-stdio.js +16 -2
- package/dist/common/zod-utils.js +62 -7
- package/dist/index.js +2 -0
- package/dist/package.json.js +1 -1
- package/dist/pactflow/client/prompts.js +19 -18
- package/dist/pactflow/client/tools.js +8 -13
- package/dist/pactflow/client.js +26 -12
- package/dist/qmetry/client/tools/testsuite-tools.js +2 -2
- package/dist/qmetry/client.js +1 -1
- package/dist/reflect/client.js +3 -3
- package/dist/reflect/prompt/sap-test.js +5 -5
- package/dist/reflect/tool/recording/add-prompt-step.js +6 -14
- package/dist/reflect/tool/recording/add-segment.js +4 -14
- package/dist/reflect/tool/recording/connect-to-session.js +3 -8
- package/dist/reflect/tool/recording/delete-previous-step.js +3 -8
- package/dist/reflect/tool/recording/get-screenshot.js +4 -14
- package/dist/reflect/tool/suites/cancel-suite-execution.js +4 -14
- package/dist/reflect/tool/suites/execute-suite.js +3 -8
- package/dist/reflect/tool/suites/get-suite-execution-status.js +4 -14
- package/dist/reflect/tool/suites/list-suite-executions.js +3 -8
- package/dist/reflect/tool/suites/list-suites.js +2 -1
- package/dist/reflect/tool/tests/get-test-status.js +3 -8
- package/dist/reflect/tool/tests/list-segments.js +5 -20
- package/dist/reflect/tool/tests/list-tests.js +2 -1
- package/dist/reflect/tool/tests/run-test.js +3 -8
- package/dist/swagger/client/api.js +11 -2
- package/dist/swagger/client/portal-types.js +0 -3
- package/dist/swagger/client/tools.js +0 -1
- package/dist/swagger/client.js +1 -1
- package/dist/zephyr/client.js +1 -1
- package/package.json +5 -4
|
@@ -1,13 +1,41 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { createServer } from "node:http";
|
|
3
|
+
import querystring from "node:querystring";
|
|
3
4
|
import { SSEServerTransport } from "@modelcontextprotocol/sdk/server/sse.js";
|
|
4
5
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
5
6
|
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
6
7
|
import { clientRegistry } from "./client-registry.js";
|
|
7
8
|
import { withRequestContext } from "./request-context.js";
|
|
8
9
|
import { SmartBearMcpServer } from "./server.js";
|
|
10
|
+
import { isDraining, registerShutdownHandler } from "./shutdown.js";
|
|
9
11
|
import { getEnvVarName } from "./transport-stdio.js";
|
|
10
|
-
import { isOptionalType } from "./zod-utils.js";
|
|
12
|
+
import { isOptionalType, getTypeDescription } from "./zod-utils.js";
|
|
13
|
+
const PROBE_HEADERS = {
|
|
14
|
+
"Content-Type": "application/json",
|
|
15
|
+
"Cache-Control": "no-store"
|
|
16
|
+
};
|
|
17
|
+
function handleHealthRequest(res) {
|
|
18
|
+
res.writeHead(200, PROBE_HEADERS);
|
|
19
|
+
res.end(
|
|
20
|
+
JSON.stringify({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() })
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
function handleReadyRequest(res, draining = isDraining) {
|
|
24
|
+
if (draining()) {
|
|
25
|
+
res.writeHead(503, PROBE_HEADERS);
|
|
26
|
+
res.end(
|
|
27
|
+
JSON.stringify({
|
|
28
|
+
status: "draining",
|
|
29
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
30
|
+
})
|
|
31
|
+
);
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
res.writeHead(200, PROBE_HEADERS);
|
|
35
|
+
res.end(
|
|
36
|
+
JSON.stringify({ status: "ready", timestamp: (/* @__PURE__ */ new Date()).toISOString() })
|
|
37
|
+
);
|
|
38
|
+
}
|
|
11
39
|
function getBaseUrl(req) {
|
|
12
40
|
const baseUrlOverride = process.env.BASE_URL;
|
|
13
41
|
if (baseUrlOverride) {
|
|
@@ -54,10 +82,11 @@ async function runHttpMode() {
|
|
|
54
82
|
const baseUrl = getBaseUrl(req);
|
|
55
83
|
const url = new URL(req.url || "/", baseUrl);
|
|
56
84
|
if (req.method === "GET" && url.pathname === "/health") {
|
|
57
|
-
res
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
85
|
+
handleHealthRequest(res);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (req.method === "GET" && url.pathname === "/ready") {
|
|
89
|
+
handleReadyRequest(res);
|
|
61
90
|
return;
|
|
62
91
|
}
|
|
63
92
|
if (req.method === "GET" && (url.pathname === "/.well-known/oauth-protected-resource" || url.pathname === "/.well-known/oauth-protected-resource/mcp")) {
|
|
@@ -89,9 +118,8 @@ async function runHttpMode() {
|
|
|
89
118
|
);
|
|
90
119
|
httpServer.listen(PORT, () => {
|
|
91
120
|
console.log(`[MCP HTTP Server] Listening on http://localhost:${PORT}`);
|
|
92
|
-
console.log(
|
|
93
|
-
|
|
94
|
-
);
|
|
121
|
+
console.log(`[MCP HTTP Server] Liveness: http://localhost:${PORT}/health`);
|
|
122
|
+
console.log(`[MCP HTTP Server] Readiness: http://localhost:${PORT}/ready`);
|
|
95
123
|
console.log(
|
|
96
124
|
`[MCP HTTP Server] Modern endpoint: http://localhost:${PORT}/mcp (Streamable HTTP)`
|
|
97
125
|
);
|
|
@@ -110,6 +138,29 @@ ${headerHelp.join("\n")}`
|
|
|
110
138
|
);
|
|
111
139
|
}
|
|
112
140
|
});
|
|
141
|
+
registerShutdownHandler("http-transport", async () => {
|
|
142
|
+
await drainHttpTransport(httpServer, transports);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
async function drainHttpTransport(httpServer, transports) {
|
|
146
|
+
console.log(
|
|
147
|
+
`[MCP][shutdown] Draining HTTP transport (${transports.size} active session(s))`
|
|
148
|
+
);
|
|
149
|
+
const serverClosed = new Promise((resolve) => {
|
|
150
|
+
httpServer.close(() => resolve());
|
|
151
|
+
});
|
|
152
|
+
httpServer.closeIdleConnections?.();
|
|
153
|
+
const transportCloses = [...transports.values()].map(async (entry) => {
|
|
154
|
+
try {
|
|
155
|
+
await entry.transport.close();
|
|
156
|
+
} catch (err) {
|
|
157
|
+
console.error("[MCP][shutdown] Error closing transport:", err);
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
await Promise.all(transportCloses);
|
|
161
|
+
httpServer.closeAllConnections?.();
|
|
162
|
+
await serverClosed;
|
|
163
|
+
console.log("[MCP][shutdown] HTTP transport drained");
|
|
113
164
|
}
|
|
114
165
|
async function parseRequestBody(req) {
|
|
115
166
|
if (req.method !== "POST") {
|
|
@@ -186,9 +237,24 @@ async function createNewTransport(req, res, transports) {
|
|
|
186
237
|
async function handleStreamableHttpRequest(req, res, transports) {
|
|
187
238
|
try {
|
|
188
239
|
const sessionId = req.headers["mcp-session-id"];
|
|
240
|
+
if (sessionId && !transports.has(sessionId)) {
|
|
241
|
+
req.resume();
|
|
242
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
243
|
+
res.end(
|
|
244
|
+
JSON.stringify({
|
|
245
|
+
jsonrpc: "2.0",
|
|
246
|
+
error: {
|
|
247
|
+
code: -32001,
|
|
248
|
+
message: "Session not found"
|
|
249
|
+
},
|
|
250
|
+
id: null
|
|
251
|
+
})
|
|
252
|
+
);
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
189
255
|
const parsedBody = await parseRequestBody(req);
|
|
190
256
|
let transport;
|
|
191
|
-
if (sessionId
|
|
257
|
+
if (sessionId) {
|
|
192
258
|
const existingTransport = getExistingTransport(
|
|
193
259
|
sessionId,
|
|
194
260
|
transports,
|
|
@@ -196,7 +262,7 @@ async function handleStreamableHttpRequest(req, res, transports) {
|
|
|
196
262
|
);
|
|
197
263
|
if (!existingTransport) return;
|
|
198
264
|
transport = existingTransport;
|
|
199
|
-
} else if (
|
|
265
|
+
} else if (req.method === "POST" && parsedBody && isInitializeRequest(parsedBody)) {
|
|
200
266
|
const newTransport = await createNewTransport(req, res, transports);
|
|
201
267
|
if (!newTransport) return;
|
|
202
268
|
transport = newTransport;
|
|
@@ -285,8 +351,14 @@ async function newServer(req, res) {
|
|
|
285
351
|
() => clientRegistry.configure(
|
|
286
352
|
server,
|
|
287
353
|
(client, key) => {
|
|
354
|
+
const queryStringName = getQueryStringName(client, key);
|
|
355
|
+
const queryParams = querystring.parse(req.url?.split("?")[1] || "");
|
|
356
|
+
let value = queryParams[queryStringName] || queryParams[queryStringName.toLowerCase()];
|
|
357
|
+
if (typeof value === "string") {
|
|
358
|
+
return value;
|
|
359
|
+
}
|
|
288
360
|
const headerName = getHeaderName(client, key);
|
|
289
|
-
|
|
361
|
+
value = req.headers[headerName] || req.headers[headerName.toLowerCase()];
|
|
290
362
|
if (typeof value === "string") {
|
|
291
363
|
return value;
|
|
292
364
|
}
|
|
@@ -338,6 +410,11 @@ function getHeaderName(client, key) {
|
|
|
338
410
|
(part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
|
339
411
|
).join("-")}`;
|
|
340
412
|
}
|
|
413
|
+
function getQueryStringName(client, key) {
|
|
414
|
+
return `${client.configPrefix.toLowerCase()}${key.split("_").map(
|
|
415
|
+
(part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()
|
|
416
|
+
).join("")}`;
|
|
417
|
+
}
|
|
341
418
|
function getHttpHeaders() {
|
|
342
419
|
const headers = /* @__PURE__ */ new Set();
|
|
343
420
|
for (const entry of clientRegistry.getAll()) {
|
|
@@ -355,15 +432,20 @@ function getHttpHeadersHelp() {
|
|
|
355
432
|
const headerName = getHeaderName(entry, configKey);
|
|
356
433
|
const requiredTag = isOptionalType(requirement) ? " (optional)" : " (required)";
|
|
357
434
|
messages.push(
|
|
358
|
-
` - ${headerName}${requiredTag}: ${requirement
|
|
435
|
+
` - ${headerName}${requiredTag}: ${getTypeDescription(requirement)}`
|
|
359
436
|
);
|
|
360
437
|
}
|
|
361
438
|
}
|
|
362
439
|
return messages;
|
|
363
440
|
}
|
|
364
441
|
export {
|
|
442
|
+
drainHttpTransport,
|
|
365
443
|
getBaseUrl,
|
|
366
444
|
getHeaderName,
|
|
445
|
+
getQueryStringName,
|
|
446
|
+
handleHealthRequest,
|
|
447
|
+
handleReadyRequest,
|
|
448
|
+
handleStreamableHttpRequest,
|
|
367
449
|
newServer,
|
|
368
450
|
runHttpMode
|
|
369
451
|
};
|
|
@@ -2,7 +2,8 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
|
|
|
2
2
|
import { clientRegistry } from "./client-registry.js";
|
|
3
3
|
import { MCP_SERVER_NAME, MCP_SERVER_VERSION } from "./info.js";
|
|
4
4
|
import { SmartBearMcpServer } from "./server.js";
|
|
5
|
-
import {
|
|
5
|
+
import { registerShutdownHandler } from "./shutdown.js";
|
|
6
|
+
import { isOptionalType, getTypeDescription } from "./zod-utils.js";
|
|
6
7
|
function getNoConfigMessage() {
|
|
7
8
|
const messages = [];
|
|
8
9
|
for (const entry of clientRegistry.getAll()) {
|
|
@@ -11,7 +12,7 @@ function getNoConfigMessage() {
|
|
|
11
12
|
const envVarName = getEnvVarName(entry, configKey);
|
|
12
13
|
const requiredTag = isOptionalType(requirement) ? " (optional)" : " (required)";
|
|
13
14
|
messages.push(
|
|
14
|
-
` - ${envVarName}${requiredTag}: ${requirement
|
|
15
|
+
` - ${envVarName}${requiredTag}: ${getTypeDescription(requirement)}`
|
|
15
16
|
);
|
|
16
17
|
}
|
|
17
18
|
}
|
|
@@ -21,6 +22,12 @@ async function runStdioMode() {
|
|
|
21
22
|
if (process.argv.includes("--version")) {
|
|
22
23
|
console.log(`${MCP_SERVER_NAME}: v${MCP_SERVER_VERSION}`);
|
|
23
24
|
process.exit(0);
|
|
25
|
+
} else if (process.argv.includes("--help")) {
|
|
26
|
+
console.log(
|
|
27
|
+
"The following environment variables can be set to configure each of the SmartBear clients:"
|
|
28
|
+
);
|
|
29
|
+
console.log(getNoConfigMessage().join("\n"));
|
|
30
|
+
process.exit(0);
|
|
24
31
|
}
|
|
25
32
|
const server = new SmartBearMcpServer();
|
|
26
33
|
const configuredCount = await clientRegistry.configure(
|
|
@@ -41,6 +48,13 @@ ${message.join("\n")}` : "No clients support environment variable configuration.
|
|
|
41
48
|
}
|
|
42
49
|
}
|
|
43
50
|
const transport = new StdioServerTransport();
|
|
51
|
+
registerShutdownHandler("stdio-transport", async () => {
|
|
52
|
+
try {
|
|
53
|
+
await transport.close();
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error("[MCP][shutdown] Error closing stdio transport:", err);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
44
58
|
transport.onmessage = (message) => {
|
|
45
59
|
if ("method" in message && message.method === "initialize") {
|
|
46
60
|
if (message.params?.protocolVersion === "2025-11-25") {
|
package/dist/common/zod-utils.js
CHANGED
|
@@ -1,20 +1,75 @@
|
|
|
1
|
-
import { ZodOptional, ZodDefault, ZodNullable } from "zod";
|
|
2
|
-
function isOptionalType(zodType) {
|
|
3
|
-
return zodType instanceof ZodOptional || zodType instanceof ZodDefault || zodType instanceof ZodNullable;
|
|
4
|
-
}
|
|
1
|
+
import { ZodOptional, ZodDefault, ZodNullable, ZodRecord, ZodString, ZodNumber, ZodBoolean, ZodArray, ZodObject, ZodEnum, ZodLiteral, ZodUnion, ZodAny } from "zod";
|
|
5
2
|
function unwrapZodType(zodType) {
|
|
6
3
|
if (zodType instanceof ZodOptional) {
|
|
7
|
-
return
|
|
4
|
+
return zodType.unwrap();
|
|
8
5
|
}
|
|
9
6
|
if (zodType instanceof ZodDefault) {
|
|
10
|
-
return
|
|
7
|
+
return zodType.unwrap();
|
|
11
8
|
}
|
|
12
9
|
if (zodType instanceof ZodNullable) {
|
|
13
|
-
return
|
|
10
|
+
return zodType.unwrap();
|
|
14
11
|
}
|
|
15
12
|
return zodType;
|
|
16
13
|
}
|
|
14
|
+
function isOptionalType(zodType) {
|
|
15
|
+
const isOptional = zodType instanceof ZodOptional || zodType instanceof ZodDefault || zodType instanceof ZodNullable;
|
|
16
|
+
if (!isOptional) {
|
|
17
|
+
const unwrapped = unwrapZodType(zodType);
|
|
18
|
+
if (unwrapped !== zodType) {
|
|
19
|
+
return isOptionalType(unwrapped);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
return isOptional;
|
|
23
|
+
}
|
|
24
|
+
function getDefaultValue(zodType) {
|
|
25
|
+
if (zodType instanceof ZodDefault) {
|
|
26
|
+
return zodType.def.defaultValue;
|
|
27
|
+
}
|
|
28
|
+
const unwrapped = unwrapZodType(zodType);
|
|
29
|
+
if (unwrapped !== zodType) {
|
|
30
|
+
return getDefaultValue(unwrapped);
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
function fullyUnwrapZodType(zodType) {
|
|
35
|
+
const unwrappedType = unwrapZodType(zodType);
|
|
36
|
+
if (unwrappedType === zodType) {
|
|
37
|
+
return unwrappedType;
|
|
38
|
+
}
|
|
39
|
+
return fullyUnwrapZodType(unwrappedType);
|
|
40
|
+
}
|
|
41
|
+
function getTypeDescription(zodType) {
|
|
42
|
+
if (zodType.description) {
|
|
43
|
+
return zodType.description;
|
|
44
|
+
}
|
|
45
|
+
const unwrapped = unwrapZodType(zodType);
|
|
46
|
+
if (unwrapped !== zodType) {
|
|
47
|
+
return getTypeDescription(unwrapped);
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
function getReadableTypeName(zodType) {
|
|
52
|
+
zodType = fullyUnwrapZodType(zodType);
|
|
53
|
+
if (zodType instanceof ZodRecord) {
|
|
54
|
+
const record = zodType;
|
|
55
|
+
return `record<${getReadableTypeName(record.def.keyType)}, ${getReadableTypeName(record.def.valueType)}>`;
|
|
56
|
+
}
|
|
57
|
+
if (zodType instanceof ZodString) return "string";
|
|
58
|
+
if (zodType instanceof ZodNumber) return "number";
|
|
59
|
+
if (zodType instanceof ZodBoolean) return "boolean";
|
|
60
|
+
if (zodType instanceof ZodArray) return "array";
|
|
61
|
+
if (zodType instanceof ZodObject) return "object";
|
|
62
|
+
if (zodType instanceof ZodEnum) return "enum";
|
|
63
|
+
if (zodType instanceof ZodLiteral) return "literal";
|
|
64
|
+
if (zodType instanceof ZodUnion) return "union";
|
|
65
|
+
if (zodType instanceof ZodAny) return "any";
|
|
66
|
+
return "any";
|
|
67
|
+
}
|
|
17
68
|
export {
|
|
69
|
+
fullyUnwrapZodType,
|
|
70
|
+
getDefaultValue,
|
|
71
|
+
getReadableTypeName,
|
|
72
|
+
getTypeDescription,
|
|
18
73
|
isOptionalType,
|
|
19
74
|
unwrapZodType
|
|
20
75
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import Bugsnag from "./common/bugsnag.js";
|
|
3
3
|
import "./common/register-clients.js";
|
|
4
|
+
import { installSignalHandlers } from "./common/shutdown.js";
|
|
4
5
|
import { runHttpMode } from "./common/transport-http.js";
|
|
5
6
|
import { runStdioMode } from "./common/transport-stdio.js";
|
|
6
7
|
const McpServerBugsnagAPIKey = process.env.MCP_SERVER_BUGSNAG_API_KEY;
|
|
7
8
|
if (McpServerBugsnagAPIKey) {
|
|
8
9
|
Bugsnag.start(McpServerBugsnagAPIKey);
|
|
9
10
|
}
|
|
11
|
+
installSignalHandlers();
|
|
10
12
|
async function main() {
|
|
11
13
|
const transportMode = process.env.MCP_TRANSPORT?.toLowerCase() || "stdio";
|
|
12
14
|
if (transportMode === "http") {
|
package/dist/package.json.js
CHANGED
|
@@ -105,27 +105,28 @@ Now provided the below OpenAPI document:-
|
|
|
105
105
|
|
|
106
106
|
Give JSON recommendations only provide the JSON block in markdown don't include any additional text.
|
|
107
107
|
`;
|
|
108
|
+
const argsSchema = z.object({
|
|
109
|
+
openAPI: z.string().describe("The OpenAPI document to generate matcher recommendations for")
|
|
110
|
+
});
|
|
108
111
|
const PROMPTS = [
|
|
109
112
|
{
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
argsSchema
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
type: "text",
|
|
124
|
-
text: OADMatcherPrompt.replace("{0}", openAPI)
|
|
113
|
+
title: "OpenAPI Matcher recommendations",
|
|
114
|
+
description: "Get OpenAPI matcher recommendations using sampling",
|
|
115
|
+
argsSchema,
|
|
116
|
+
callback: (args) => {
|
|
117
|
+
const params = argsSchema.parse(args);
|
|
118
|
+
return {
|
|
119
|
+
messages: [
|
|
120
|
+
{
|
|
121
|
+
role: "user",
|
|
122
|
+
content: {
|
|
123
|
+
type: "text",
|
|
124
|
+
text: OADMatcherPrompt.replace("{0}", params.openAPI)
|
|
125
|
+
}
|
|
125
126
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
}
|
|
127
|
+
]
|
|
128
|
+
};
|
|
129
|
+
}
|
|
129
130
|
}
|
|
130
131
|
];
|
|
131
132
|
export {
|
|
@@ -27,14 +27,9 @@ const TOOLS = [
|
|
|
27
27
|
title: "Get Provider States",
|
|
28
28
|
summary: "Retrieve the states of a specific provider",
|
|
29
29
|
purpose: "A provider state in Pact defines the specific preconditions that must be met on the provider side before a consumer-provider interaction can be tested. It sets up the provider in the right context—such as ensuring a particular user or record exists—so that the provider can return the response the consumer expects. This makes contract tests reliable, repeatable, and isolated by injecting or configuring the necessary data and conditions directly into the provider before each test runs.",
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
type: z.string(),
|
|
34
|
-
description: "name of the provider to retrieve states for",
|
|
35
|
-
required: true
|
|
36
|
-
}
|
|
37
|
-
],
|
|
30
|
+
inputSchema: z.object({
|
|
31
|
+
provider: z.string().describe("name of the provider to retrieve states for")
|
|
32
|
+
}),
|
|
38
33
|
handler: "getProviderStates",
|
|
39
34
|
clients: ["pactflow", "pact_broker"]
|
|
40
35
|
},
|
|
@@ -256,7 +251,7 @@ const TOOLS = [
|
|
|
256
251
|
clients: ["pactflow"]
|
|
257
252
|
},
|
|
258
253
|
{
|
|
259
|
-
title: "Get BDCT Consumer
|
|
254
|
+
title: "Get BDCT Consumer by Consumer Version",
|
|
260
255
|
summary: "Fetch the consumer Pact contract for a specific consumer-provider version pair in Bi-Directional Contract Testing.",
|
|
261
256
|
purpose: "Retrieve the Pact contract published by a specific consumer version, in the context of a specific provider version. Use this when you need the exact consumer contract that was compared against a given provider spec.",
|
|
262
257
|
inputSchema: GetBiDirectionalConsumerProviderVersionSchema,
|
|
@@ -264,7 +259,7 @@ const TOOLS = [
|
|
|
264
259
|
clients: ["pactflow"]
|
|
265
260
|
},
|
|
266
261
|
{
|
|
267
|
-
title: "Get BDCT Provider
|
|
262
|
+
title: "Get BDCT Provider by Consumer Version",
|
|
268
263
|
summary: "Fetch the provider OpenAPI contract for a specific consumer-provider version pair in Bi-Directional Contract Testing.",
|
|
269
264
|
purpose: "Retrieve the provider's OpenAPI spec in the context of a specific consumer version pair. Useful when investigating why a particular consumer-provider combination failed cross-contract verification.",
|
|
270
265
|
inputSchema: GetBiDirectionalConsumerProviderVersionSchema,
|
|
@@ -272,7 +267,7 @@ const TOOLS = [
|
|
|
272
267
|
clients: ["pactflow"]
|
|
273
268
|
},
|
|
274
269
|
{
|
|
275
|
-
title: "Get BDCT Provider
|
|
270
|
+
title: "Get BDCT Provider Check Results by Consumer",
|
|
276
271
|
summary: "Fetch the provider contract self-verification results for a specific consumer-provider version pair in Bi-Directional Contract Testing.",
|
|
277
272
|
purpose: "Retrieve the provider's self-verification results in the context of a specific consumer version pair. Use when diagnosing failures for a particular consumer-provider combination.",
|
|
278
273
|
inputSchema: GetBiDirectionalConsumerProviderVersionSchema,
|
|
@@ -280,7 +275,7 @@ const TOOLS = [
|
|
|
280
275
|
clients: ["pactflow"]
|
|
281
276
|
},
|
|
282
277
|
{
|
|
283
|
-
title: "Get BDCT Consumer
|
|
278
|
+
title: "Get BDCT Consumer Pact Test Results by Consumer",
|
|
284
279
|
summary: "Fetch the consumer contract verification results for a specific consumer-provider version pair in Bi-Directional Contract Testing.",
|
|
285
280
|
purpose: "Retrieve the results of comparing a specific consumer version's Pact against the provider's OpenAPI spec. Shows exactly which interactions passed or failed the cross-contract comparison.",
|
|
286
281
|
inputSchema: GetBiDirectionalConsumerProviderVersionSchema,
|
|
@@ -288,7 +283,7 @@ const TOOLS = [
|
|
|
288
283
|
clients: ["pactflow"]
|
|
289
284
|
},
|
|
290
285
|
{
|
|
291
|
-
title: "Get BDCT
|
|
286
|
+
title: "Get BDCT X-Contract Test Results by Consumer",
|
|
292
287
|
summary: "Fetch the cross-contract verification results for a specific consumer-provider version pair in Bi-Directional Contract Testing.",
|
|
293
288
|
purpose: "Retrieve the precise cross-contract comparison outcome between a specific consumer version and provider version. This is the most granular BDCT result — use it to understand exactly why a specific consumer-provider pairing succeeded or failed, and which interactions were incompatible.",
|
|
294
289
|
inputSchema: GetBiDirectionalConsumerProviderVersionSchema,
|
package/dist/pactflow/client.js
CHANGED
|
@@ -16,7 +16,7 @@ const ConfigurationSchema = zod__default.object({
|
|
|
16
16
|
});
|
|
17
17
|
class PactflowClient {
|
|
18
18
|
name = "Contract Testing";
|
|
19
|
-
|
|
19
|
+
capabilityPrefix = "contract-testing";
|
|
20
20
|
configPrefix = "Pact-Broker";
|
|
21
21
|
config = ConfigurationSchema;
|
|
22
22
|
token;
|
|
@@ -136,10 +136,17 @@ class PactflowClient {
|
|
|
136
136
|
* @throws Error if the request fails or returns a non-OK response.
|
|
137
137
|
*/
|
|
138
138
|
async checkAIEntitlements() {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
139
|
+
if (this.aiBaseUrl) {
|
|
140
|
+
return await this.fetchJson(
|
|
141
|
+
`${this.aiBaseUrl}/entitlement`,
|
|
142
|
+
{
|
|
143
|
+
method: "GET",
|
|
144
|
+
errorContext: "PactFlow AI Entitlements Request"
|
|
145
|
+
}
|
|
146
|
+
);
|
|
147
|
+
} else {
|
|
148
|
+
return null;
|
|
149
|
+
}
|
|
143
150
|
}
|
|
144
151
|
/**
|
|
145
152
|
* Polls the given status URL with a HEAD request to check operation progress.
|
|
@@ -2060,7 +2067,7 @@ class PactflowClient {
|
|
|
2060
2067
|
let disablePactflowAItools = false;
|
|
2061
2068
|
try {
|
|
2062
2069
|
const entitlement = await this.checkAIEntitlements();
|
|
2063
|
-
if (!entitlement.aiEnabled) {
|
|
2070
|
+
if (entitlement && !entitlement.aiEnabled) {
|
|
2064
2071
|
disablePactflowAItools = true;
|
|
2065
2072
|
}
|
|
2066
2073
|
} catch (error) {
|
|
@@ -2074,8 +2081,8 @@ class PactflowClient {
|
|
|
2074
2081
|
if (tool.tags && disablePactflowAItools && tool.tags.includes("pactflow-ai")) {
|
|
2075
2082
|
continue;
|
|
2076
2083
|
}
|
|
2077
|
-
const { handler, clients: _, formatResponse, ...
|
|
2078
|
-
register(
|
|
2084
|
+
const { handler, clients: _, formatResponse, ...toolParams } = tool;
|
|
2085
|
+
register(toolParams, async (args, _extra) => {
|
|
2079
2086
|
const handler_fn = this[handler];
|
|
2080
2087
|
if (typeof handler_fn !== "function") {
|
|
2081
2088
|
throw new Error(`Handler '${handler}' not found on PactClient`);
|
|
@@ -2100,10 +2107,17 @@ class PactflowClient {
|
|
|
2100
2107
|
*
|
|
2101
2108
|
* @param register - The function used to register prompts.
|
|
2102
2109
|
*/
|
|
2103
|
-
registerPrompts(register) {
|
|
2104
|
-
|
|
2105
|
-
register(
|
|
2106
|
-
|
|
2110
|
+
async registerPrompts(register) {
|
|
2111
|
+
for (const prompt of PROMPTS) {
|
|
2112
|
+
register(
|
|
2113
|
+
{
|
|
2114
|
+
title: prompt.title,
|
|
2115
|
+
description: prompt.description,
|
|
2116
|
+
argsSchema: prompt.argsSchema
|
|
2117
|
+
},
|
|
2118
|
+
prompt.callback
|
|
2119
|
+
);
|
|
2120
|
+
}
|
|
2107
2121
|
}
|
|
2108
2122
|
}
|
|
2109
2123
|
export {
|
|
@@ -926,8 +926,8 @@ const TESTSUITE_TOOLS = [
|
|
|
926
926
|
entityType: "TCR",
|
|
927
927
|
qmTsRunId: "2720260",
|
|
928
928
|
runStatusID: 123266,
|
|
929
|
-
username: "
|
|
930
|
-
password: "
|
|
929
|
+
username: "test.user",
|
|
930
|
+
password: "password",
|
|
931
931
|
isBulkOperation: false
|
|
932
932
|
},
|
|
933
933
|
expectedOutput: "Test case run status updated with Part 11 Compliance authentication"
|
package/dist/qmetry/client.js
CHANGED
package/dist/reflect/client.js
CHANGED
|
@@ -27,7 +27,7 @@ class ReflectClient {
|
|
|
27
27
|
sessionStates = /* @__PURE__ */ new Map();
|
|
28
28
|
mcpSessionConnections = /* @__PURE__ */ new Map();
|
|
29
29
|
name = "Reflect";
|
|
30
|
-
|
|
30
|
+
capabilityPrefix = "reflect";
|
|
31
31
|
configPrefix = "Reflect";
|
|
32
32
|
config = ConfigurationSchema;
|
|
33
33
|
async configure(_server, config, _cache) {
|
|
@@ -136,10 +136,10 @@ class ReflectClient {
|
|
|
136
136
|
register(tool.specification, tool.handle);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
|
-
registerPrompts(register) {
|
|
139
|
+
async registerPrompts(register) {
|
|
140
140
|
const prompts = [new SapTest(this)];
|
|
141
141
|
for (const prompt of prompts) {
|
|
142
|
-
register(prompt.
|
|
142
|
+
register(prompt.specification, prompt.callback);
|
|
143
143
|
}
|
|
144
144
|
}
|
|
145
145
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
1
2
|
import { Prompt } from "../../common/prompts.js";
|
|
2
3
|
class SapTest extends Prompt {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
title: "Reflect SAP Test",
|
|
4
|
+
specification = {
|
|
5
|
+
title: "SAP Test",
|
|
6
6
|
description: "Guidelines for creating a Reflect test against an SAP S4/HANA or SAP BTP application.",
|
|
7
|
-
argsSchema: {}
|
|
7
|
+
argsSchema: z.object({})
|
|
8
8
|
};
|
|
9
|
-
callback = () => ({
|
|
9
|
+
callback = (_args) => ({
|
|
10
10
|
messages: [
|
|
11
11
|
{
|
|
12
12
|
role: "user",
|
|
@@ -7,20 +7,12 @@ class AddPromptStep extends Tool {
|
|
|
7
7
|
summary: "Add a natural language prompt step to an active Reflect recording session",
|
|
8
8
|
readOnly: false,
|
|
9
9
|
idempotent: false,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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
|
-
]
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
sessionId: z.string().describe("The ID of the Reflect recording session"),
|
|
12
|
+
prompt: z.string().describe(
|
|
13
|
+
"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')."
|
|
14
|
+
)
|
|
15
|
+
})
|
|
24
16
|
};
|
|
25
17
|
handle = async (args) => {
|
|
26
18
|
const { sessionId, prompt } = args;
|
|
@@ -7,20 +7,10 @@ class AddSegment extends Tool {
|
|
|
7
7
|
summary: "Insert a reusable test segment into an active Reflect recording session",
|
|
8
8
|
readOnly: false,
|
|
9
9
|
idempotent: false,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
-
]
|
|
10
|
+
inputSchema: z.object({
|
|
11
|
+
sessionId: z.string().describe("The ID of the Reflect recording session"),
|
|
12
|
+
segmentId: z.number().describe("The ID of the segment to add")
|
|
13
|
+
})
|
|
24
14
|
};
|
|
25
15
|
handle = async (args) => {
|
|
26
16
|
const { sessionId, segmentId } = args;
|
|
@@ -16,14 +16,9 @@ class ConnectToSession extends Tool {
|
|
|
16
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
17
|
readOnly: false,
|
|
18
18
|
idempotent: true,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
type: z.string(),
|
|
23
|
-
description: "The ID of the Reflect recording session to connect to",
|
|
24
|
-
required: true
|
|
25
|
-
}
|
|
26
|
-
]
|
|
19
|
+
inputSchema: z.object({
|
|
20
|
+
sessionId: z.string().describe("The ID of the Reflect recording session to connect to")
|
|
21
|
+
})
|
|
27
22
|
};
|
|
28
23
|
handle = async (args, extra) => {
|
|
29
24
|
const { sessionId } = args;
|