@smartbear/mcp 0.19.0 → 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.
Files changed (42) hide show
  1. package/README.md +15 -2
  2. package/assets/icon.png +0 -0
  3. package/dist/bugsnag/client.js +19 -12
  4. package/dist/collaborator/client.js +10 -10
  5. package/dist/common/client-registry.js +2 -2
  6. package/dist/common/server.js +74 -111
  7. package/dist/common/shutdown.js +165 -0
  8. package/dist/common/transport-http.js +94 -12
  9. package/dist/common/transport-stdio.js +16 -2
  10. package/dist/common/zod-utils.js +62 -7
  11. package/dist/index.js +2 -0
  12. package/dist/package.json.js +1 -1
  13. package/dist/pactflow/client/prompts.js +19 -18
  14. package/dist/pactflow/client/tools.js +8 -13
  15. package/dist/pactflow/client.js +26 -12
  16. package/dist/qmetry/client/tools/testsuite-tools.js +2 -2
  17. package/dist/qmetry/client.js +1 -1
  18. package/dist/reflect/client.js +3 -3
  19. package/dist/reflect/prompt/sap-test.js +5 -5
  20. package/dist/reflect/tool/recording/add-prompt-step.js +6 -14
  21. package/dist/reflect/tool/recording/add-segment.js +4 -14
  22. package/dist/reflect/tool/recording/connect-to-session.js +3 -8
  23. package/dist/reflect/tool/recording/delete-previous-step.js +3 -8
  24. package/dist/reflect/tool/recording/get-screenshot.js +4 -14
  25. package/dist/reflect/tool/suites/cancel-suite-execution.js +4 -14
  26. package/dist/reflect/tool/suites/execute-suite.js +3 -8
  27. package/dist/reflect/tool/suites/get-suite-execution-status.js +4 -14
  28. package/dist/reflect/tool/suites/list-suite-executions.js +3 -8
  29. package/dist/reflect/tool/suites/list-suites.js +2 -1
  30. package/dist/reflect/tool/tests/get-test-status.js +3 -8
  31. package/dist/reflect/tool/tests/list-segments.js +5 -20
  32. package/dist/reflect/tool/tests/list-tests.js +2 -1
  33. package/dist/reflect/tool/tests/run-test.js +3 -8
  34. package/dist/swagger/client/api.js +11 -2
  35. package/dist/swagger/client/portal-types.js +0 -3
  36. package/dist/swagger/client/tools.js +0 -1
  37. package/dist/swagger/client.js +1 -1
  38. package/dist/zephyr/client.js +1 -1
  39. package/dist/zephyr/common/rest-api-schemas.js +8 -28
  40. package/dist/zephyr/tool/test-case/update-test-case.js +23 -1
  41. package/dist/zephyr/tool/test-cycle/update-test-cycle.js +17 -2
  42. 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.writeHead(200, { "Content-Type": "application/json" });
58
- res.end(
59
- JSON.stringify({ status: "ok", timestamp: (/* @__PURE__ */ new Date()).toISOString() })
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
- `[MCP HTTP Server] Health check: http://localhost:${PORT}/health`
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 && transports.has(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 (!sessionId && req.method === "POST" && parsedBody && isInitializeRequest(parsedBody)) {
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
- const value = req.headers[headerName] || req.headers[headerName.toLowerCase()];
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.description}`
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 { isOptionalType } from "./zod-utils.js";
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.description}`
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") {
@@ -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 unwrapZodType(zodType.unwrap());
4
+ return zodType.unwrap();
8
5
  }
9
6
  if (zodType instanceof ZodDefault) {
10
- return unwrapZodType(zodType.unwrap());
7
+ return zodType.unwrap();
11
8
  }
12
9
  if (zodType instanceof ZodNullable) {
13
- return unwrapZodType(zodType.unwrap());
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") {
@@ -1,4 +1,4 @@
1
- const version = "0.19.0";
1
+ const version = "0.20.0";
2
2
  const config = { "mcpServerName": "SmartBear MCP Server" };
3
3
  const packageJson = {
4
4
  version,
@@ -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
- name: "OpenAPI Matcher recommendations",
111
- params: {
112
- description: "Get OpenAPI matcher recommendations using sampling",
113
- title: "OpenAPI Matcher recommendations",
114
- argsSchema: {
115
- openAPI: z.string()
116
- }
117
- },
118
- callback: ({ openAPI }) => ({
119
- messages: [
120
- {
121
- role: "user",
122
- content: {
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
- parameters: [
31
- {
32
- name: "provider",
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 Contract by Consumer Version",
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 Contract by Consumer Version",
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 Contract Verification Results by Consumer Version",
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 Contract Verification Results by Consumer Version",
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 Cross-Contract Verification Results by Consumer Version",
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,
@@ -16,7 +16,7 @@ const ConfigurationSchema = zod__default.object({
16
16
  });
17
17
  class PactflowClient {
18
18
  name = "Contract Testing";
19
- toolPrefix = "contract-testing";
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
- return await this.fetchJson(`${this.aiBaseUrl}/entitlement`, {
140
- method: "GET",
141
- errorContext: "PactFlow AI Entitlements Request"
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, ...toolparams } = tool;
2078
- register(toolparams, async (args, _extra) => {
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
- PROMPTS.forEach((prompt) => {
2105
- register(prompt.name, prompt.params, prompt.callback);
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: "dhaval.mistry",
930
- password: "Ispl123#",
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"
@@ -13,7 +13,7 @@ const ConfigurationSchema = zod__default.object({
13
13
  });
14
14
  class QmetryClient {
15
15
  name = "QMetry";
16
- toolPrefix = "qmetry";
16
+ capabilityPrefix = "qmetry";
17
17
  configPrefix = "Qmetry";
18
18
  config = ConfigurationSchema;
19
19
  token;
@@ -27,7 +27,7 @@ class ReflectClient {
27
27
  sessionStates = /* @__PURE__ */ new Map();
28
28
  mcpSessionConnections = /* @__PURE__ */ new Map();
29
29
  name = "Reflect";
30
- toolPrefix = "reflect";
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.name, prompt.params, prompt.callback);
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
- name = "reflect-sap-test";
4
- params = {
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
- 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
- ]
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
- 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
- ]
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
- 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
- ]
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;