@frontmcp/testing 0.12.1 → 1.0.0-beta.1

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/assertions/mcp-assertions.d.ts +1 -1
  2. package/assertions/mcp-assertions.d.ts.map +1 -1
  3. package/client/mcp-test-client.builder.d.ts +1 -1
  4. package/client/mcp-test-client.d.ts.map +1 -1
  5. package/client/mcp-test-client.types.d.ts +3 -3
  6. package/client/mcp-test-client.types.d.ts.map +1 -1
  7. package/errors/index.d.ts.map +1 -1
  8. package/esm/fixtures/index.mjs +239 -36
  9. package/esm/index.mjs +267 -112
  10. package/esm/matchers/index.mjs +8 -32
  11. package/esm/package.json +5 -5
  12. package/esm/perf/index.mjs +239 -36
  13. package/esm/setup.mjs +8 -32
  14. package/example-tools/index.d.ts +1 -1
  15. package/example-tools/index.d.ts.map +1 -1
  16. package/example-tools/tool-configs.d.ts +4 -10
  17. package/example-tools/tool-configs.d.ts.map +1 -1
  18. package/fixtures/index.js +232 -36
  19. package/index.d.ts +2 -1
  20. package/index.d.ts.map +1 -1
  21. package/index.js +264 -114
  22. package/matchers/index.js +8 -32
  23. package/package.json +5 -5
  24. package/perf/index.js +232 -36
  25. package/platform/platform-client-info.d.ts +2 -1
  26. package/platform/platform-client-info.d.ts.map +1 -1
  27. package/platform/platform-types.d.ts +11 -16
  28. package/platform/platform-types.d.ts.map +1 -1
  29. package/playwright/index.d.ts +1 -1
  30. package/raw-client/index.d.ts +7 -0
  31. package/raw-client/index.d.ts.map +1 -0
  32. package/server/index.d.ts +1 -1
  33. package/server/index.d.ts.map +1 -1
  34. package/server/port-registry.d.ts +24 -6
  35. package/server/port-registry.d.ts.map +1 -1
  36. package/server/test-server.d.ts +1 -1
  37. package/server/test-server.d.ts.map +1 -1
  38. package/setup.js +8 -32
  39. package/ui/ui-assertions.d.ts +3 -4
  40. package/ui/ui-assertions.d.ts.map +1 -1
  41. package/ui/ui-matchers.d.ts +1 -2
  42. package/ui/ui-matchers.d.ts.map +1 -1
package/esm/index.mjs CHANGED
@@ -1,3 +1,10 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
1
8
  // libs/testing/src/platform/platform-client-info.ts
2
9
  var MCP_APPS_EXTENSION_KEY = "io.modelcontextprotocol/ui";
3
10
  function getPlatformClientInfo(platform) {
@@ -83,7 +90,7 @@ function getPlatformCapabilities(platform) {
83
90
  ...baseCapabilities,
84
91
  experimental: {
85
92
  [MCP_APPS_EXTENSION_KEY]: {
86
- mimeTypes: ["text/html+mcp"]
93
+ mimeTypes: ["text/html;profile=mcp-app"]
87
94
  }
88
95
  }
89
96
  };
@@ -204,7 +211,7 @@ var McpTestClientBuilder = class {
204
211
  * .withCapabilities({
205
212
  * sampling: {},
206
213
  * experimental: {
207
- * 'io.modelcontextprotocol/ui': { mimeTypes: ['text/html+mcp'] }
214
+ * 'io.modelcontextprotocol/ui': { mimeTypes: ['text/html;profile=mcp-app'] }
208
215
  * }
209
216
  * })
210
217
  * .buildAndConnect();
@@ -1250,11 +1257,24 @@ var McpTestClient = class {
1250
1257
  this.log("debug", `Connecting to ${this.config.baseUrl}...`);
1251
1258
  this.transport = this.createTransport();
1252
1259
  await this.transport.connect();
1253
- const initResponse = await this.initialize();
1254
- if (!initResponse.success || !initResponse.data) {
1255
- throw new Error(`Failed to initialize MCP connection: ${initResponse.error?.message ?? "Unknown error"}`);
1260
+ const maxRetries = 3;
1261
+ const retryDelayMs = 500;
1262
+ let lastError;
1263
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1264
+ const initResponse = await this.initialize();
1265
+ if (initResponse.success && initResponse.data) {
1266
+ this.initResult = initResponse.data;
1267
+ break;
1268
+ }
1269
+ lastError = initResponse.error?.message ?? "Unknown error";
1270
+ if (attempt < maxRetries) {
1271
+ this.log("debug", `MCP init attempt ${attempt} failed (${lastError}), retrying in ${retryDelayMs}ms...`);
1272
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
1273
+ }
1274
+ }
1275
+ if (!this.initResult) {
1276
+ throw new Error(`Failed to initialize MCP connection after ${maxRetries} attempts: ${lastError}`);
1256
1277
  }
1257
- this.initResult = initResponse.data;
1258
1278
  this._sessionId = this.transport.getSessionId();
1259
1279
  this._sessionInfo = {
1260
1280
  id: this._sessionId ?? `session-${Date.now()}`,
@@ -1945,7 +1965,7 @@ var McpTestClient = class {
1945
1965
  const raw = response.data ?? { content: [] };
1946
1966
  const isError2 = !response.success || raw.isError === true;
1947
1967
  const meta = raw._meta;
1948
- const hasUI = meta?.["ui/html"] !== void 0 || meta?.["ui/component"] !== void 0 || meta?.["openai/html"] !== void 0 || meta?.["frontmcp/html"] !== void 0;
1968
+ const hasUI = meta?.["ui/html"] !== void 0 || meta?.["ui/component"] !== void 0;
1949
1969
  const structuredContent = raw["structuredContent"];
1950
1970
  return {
1951
1971
  raw,
@@ -3799,6 +3819,7 @@ var MockCimdServer = class {
3799
3819
 
3800
3820
  // libs/testing/src/server/test-server.ts
3801
3821
  import { spawn } from "child_process";
3822
+ import { sha256Hex } from "@frontmcp/utils";
3802
3823
 
3803
3824
  // libs/testing/src/errors/index.ts
3804
3825
  var TestClientError = class extends Error {
@@ -3878,9 +3899,16 @@ var E2E_PORT_RANGES = {
3878
3899
  "demo-e2e-agents": { start: 50270, size: 10 },
3879
3900
  "demo-e2e-transport-recreation": { start: 50280, size: 10 },
3880
3901
  "demo-e2e-jobs": { start: 50290, size: 10 },
3881
- // Infrastructure E2E tests (50300-50399)
3902
+ // Infrastructure E2E tests (50300-50409)
3882
3903
  "demo-e2e-redis": { start: 50300, size: 10 },
3883
3904
  "demo-e2e-serverless": { start: 50310, size: 10 },
3905
+ "demo-e2e-uipack": { start: 50320, size: 10 },
3906
+ "demo-e2e-agent-adapters": { start: 50330, size: 10 },
3907
+ "demo-e2e-guard": { start: 50340, size: 10 },
3908
+ // ESM E2E tests (50400-50449)
3909
+ "esm-package-server": { start: 50400, size: 10 },
3910
+ "esm-package-server-hot-reload": { start: 50410, size: 10 },
3911
+ "esm-package-server-cli": { start: 50420, size: 10 },
3884
3912
  // Mock servers and utilities (50900-50999)
3885
3913
  "mock-oauth": { start: 50900, size: 10 },
3886
3914
  "mock-api": { start: 50910, size: 10 },
@@ -3952,7 +3980,7 @@ async function tryReservePort(port, project) {
3952
3980
  server.once("error", () => {
3953
3981
  resolve(false);
3954
3982
  });
3955
- server.listen(port, "::", () => {
3983
+ server.listen(port, () => {
3956
3984
  reservedPorts.set(port, {
3957
3985
  port,
3958
3986
  project,
@@ -3993,7 +4021,7 @@ async function isPortAvailable(port) {
3993
4021
  server.once("error", () => {
3994
4022
  resolve(false);
3995
4023
  });
3996
- server.listen(port, "::", () => {
4024
+ server.listen(port, () => {
3997
4025
  server.close(() => {
3998
4026
  resolve(true);
3999
4027
  });
@@ -4031,15 +4059,37 @@ var TestServer = class _TestServer {
4031
4059
  */
4032
4060
  static async start(options) {
4033
4061
  const project = options.project ?? "default";
4034
- const { port, release } = await reservePort(project, options.port);
4035
- const server = new _TestServer(options, port, release);
4036
- try {
4037
- await server.startProcess();
4038
- } catch (error) {
4039
- await server.stop();
4040
- throw error;
4062
+ const maxAttempts = 3;
4063
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4064
+ const { port, release } = await reservePort(project, options.port);
4065
+ const server = new _TestServer(options, port, release);
4066
+ try {
4067
+ await server.startProcess();
4068
+ return server;
4069
+ } catch (error) {
4070
+ try {
4071
+ await server.stop();
4072
+ } catch (cleanupError) {
4073
+ if (options.debug || DEBUG_SERVER) {
4074
+ const msg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
4075
+ console.warn(`[TestServer] Cleanup failed after startup error: ${msg}`);
4076
+ }
4077
+ }
4078
+ const isEADDRINUSE = error instanceof Error && (error.message.includes("EADDRINUSE") || server.getLogs().some((l) => l.includes("EADDRINUSE")));
4079
+ if (isEADDRINUSE && attempt < maxAttempts) {
4080
+ const delayMs = attempt * 500;
4081
+ if (options.debug || DEBUG_SERVER) {
4082
+ console.warn(
4083
+ `[TestServer] EADDRINUSE on port ${port}, retrying in ${delayMs}ms (attempt ${attempt}/${maxAttempts})`
4084
+ );
4085
+ }
4086
+ await sleep2(delayMs);
4087
+ continue;
4088
+ }
4089
+ throw error;
4090
+ }
4041
4091
  }
4042
- return server;
4092
+ throw new Error(`[TestServer] Failed to start after ${maxAttempts} attempts`);
4043
4093
  }
4044
4094
  /**
4045
4095
  * Start an Nx project as test server
@@ -4050,22 +4100,44 @@ var TestServer = class _TestServer {
4050
4100
  `Invalid project name: ${project}. Must contain only alphanumeric, underscore, and hyphen characters.`
4051
4101
  );
4052
4102
  }
4053
- const { port, release } = await reservePort(project, options.port);
4054
- const serverOptions = {
4055
- ...options,
4056
- port,
4057
- project,
4058
- command: `npx nx serve ${project} --port ${port}`,
4059
- cwd: options.cwd ?? process.cwd()
4060
- };
4061
- const server = new _TestServer(serverOptions, port, release);
4062
- try {
4063
- await server.startProcess();
4064
- } catch (error) {
4065
- await server.stop();
4066
- throw error;
4103
+ const maxAttempts = 3;
4104
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4105
+ const { port, release } = await reservePort(project, options.port);
4106
+ const serverOptions = {
4107
+ ...options,
4108
+ port,
4109
+ project,
4110
+ command: `npx nx serve ${project} --port ${port}`,
4111
+ cwd: options.cwd ?? process.cwd()
4112
+ };
4113
+ const server = new _TestServer(serverOptions, port, release);
4114
+ try {
4115
+ await server.startProcess();
4116
+ return server;
4117
+ } catch (error) {
4118
+ try {
4119
+ await server.stop();
4120
+ } catch (cleanupError) {
4121
+ if (options.debug || DEBUG_SERVER) {
4122
+ const msg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
4123
+ console.warn(`[TestServer] Cleanup failed after startup error: ${msg}`);
4124
+ }
4125
+ }
4126
+ const isEADDRINUSE = error instanceof Error && (error.message.includes("EADDRINUSE") || server.getLogs().some((l) => l.includes("EADDRINUSE")));
4127
+ if (isEADDRINUSE && attempt < maxAttempts) {
4128
+ const delayMs = attempt * 500;
4129
+ if (options.debug || DEBUG_SERVER) {
4130
+ console.warn(
4131
+ `[TestServer] EADDRINUSE on port ${port}, retrying in ${delayMs}ms (attempt ${attempt}/${maxAttempts})`
4132
+ );
4133
+ }
4134
+ await sleep2(delayMs);
4135
+ continue;
4136
+ }
4137
+ throw error;
4138
+ }
4067
4139
  }
4068
- return server;
4140
+ throw new Error(`[TestServer] Failed to start after ${maxAttempts} attempts`);
4069
4141
  }
4070
4142
  /**
4071
4143
  * Create a test server connected to an already running server
@@ -4102,7 +4174,21 @@ var TestServer = class _TestServer {
4102
4174
  }
4103
4175
  if (this.process) {
4104
4176
  this.log("Stopping server...");
4105
- this.process.kill("SIGTERM");
4177
+ if (this.process.exitCode !== null || this.process.signalCode !== null) {
4178
+ this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
4179
+ this.process = null;
4180
+ return;
4181
+ }
4182
+ const pid = this.process.pid;
4183
+ try {
4184
+ if (pid !== void 0) {
4185
+ process.kill(-pid, "SIGTERM");
4186
+ } else {
4187
+ this.process.kill("SIGTERM");
4188
+ }
4189
+ } catch {
4190
+ this.process.kill("SIGTERM");
4191
+ }
4106
4192
  const exitPromise = new Promise((resolve) => {
4107
4193
  if (this.process) {
4108
4194
  this.process.once("exit", () => resolve());
@@ -4113,7 +4199,16 @@ var TestServer = class _TestServer {
4113
4199
  const killTimeout = setTimeout(() => {
4114
4200
  if (this.process) {
4115
4201
  this.log("Force killing server after timeout...");
4116
- this.process.kill("SIGKILL");
4202
+ const killPid = this.process.pid;
4203
+ try {
4204
+ if (killPid !== void 0) {
4205
+ process.kill(-killPid, "SIGKILL");
4206
+ } else {
4207
+ this.process.kill("SIGKILL");
4208
+ }
4209
+ } catch {
4210
+ this.process.kill("SIGKILL");
4211
+ }
4117
4212
  }
4118
4213
  }, 5e3);
4119
4214
  await exitPromise;
@@ -4178,14 +4273,17 @@ var TestServer = class _TestServer {
4178
4273
  ...this.options.env,
4179
4274
  PORT: String(this.options.port)
4180
4275
  };
4276
+ const runtimeEnv = withWorkspaceProtocolFallback(env, this.options.cwd);
4181
4277
  if (this.portRelease) {
4182
4278
  await this.portRelease();
4183
4279
  this.portRelease = null;
4280
+ await sleep2(300);
4184
4281
  }
4185
4282
  this.process = spawn(this.options.command, [], {
4186
4283
  cwd: this.options.cwd,
4187
- env,
4284
+ env: runtimeEnv,
4188
4285
  shell: true,
4286
+ detached: true,
4189
4287
  stdio: ["pipe", "pipe", "pipe"]
4190
4288
  });
4191
4289
  if (this.process.pid !== void 0) {
@@ -4337,6 +4435,111 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
4337
4435
  function sleep2(ms) {
4338
4436
  return new Promise((resolve) => setTimeout(resolve, ms));
4339
4437
  }
4438
+ function withWorkspaceProtocolFallback(env, cwd) {
4439
+ if (findInstalledProtocolPackageDir(cwd)) {
4440
+ return env;
4441
+ }
4442
+ try {
4443
+ const workspacePackageDir = findWorkspaceProtocolDir(cwd);
4444
+ if (!workspacePackageDir) {
4445
+ return env;
4446
+ }
4447
+ ensureWorkspaceProtocolLink(cwd, workspacePackageDir);
4448
+ if (findInstalledProtocolPackageDir(cwd)) {
4449
+ return env;
4450
+ }
4451
+ return withProtocolNodePathAlias(env, cwd, workspacePackageDir);
4452
+ } catch (err) {
4453
+ if (DEBUG_SERVER) {
4454
+ console.error(
4455
+ `[TestServer] Workspace protocol fallback failed: ${err instanceof Error ? err.message : String(err)}`
4456
+ );
4457
+ }
4458
+ return env;
4459
+ }
4460
+ }
4461
+ function ensureWorkspaceProtocolLink(cwd, workspacePackageDir) {
4462
+ const fs = __require("node:fs");
4463
+ const path = __require("node:path");
4464
+ const nodeModulesDir = findWorkspaceNodeModulesDir(cwd);
4465
+ if (!nodeModulesDir) {
4466
+ return;
4467
+ }
4468
+ const scopeDir = path.join(nodeModulesDir, "@frontmcp");
4469
+ const aliasPackageDir = path.join(scopeDir, "protocol");
4470
+ if (fs.existsSync(aliasPackageDir)) {
4471
+ return;
4472
+ }
4473
+ fs.mkdirSync(scopeDir, { recursive: true });
4474
+ try {
4475
+ fs.symlinkSync(workspacePackageDir, aliasPackageDir, process.platform === "win32" ? "junction" : "dir");
4476
+ } catch (error) {
4477
+ const err = error;
4478
+ if (err.code !== "EEXIST") {
4479
+ throw error;
4480
+ }
4481
+ }
4482
+ }
4483
+ function withProtocolNodePathAlias(env, cwd, workspacePackageDir) {
4484
+ const fs = __require("node:fs");
4485
+ const os = __require("node:os");
4486
+ const path = __require("node:path");
4487
+ const aliasRoot = path.join(os.tmpdir(), "frontmcp-test-node-path", sha256Hex(cwd).slice(0, 12));
4488
+ const scopeDir = path.join(aliasRoot, "@frontmcp");
4489
+ const aliasPackageDir = path.join(scopeDir, "protocol");
4490
+ fs.mkdirSync(scopeDir, { recursive: true });
4491
+ try {
4492
+ if (!fs.existsSync(aliasPackageDir)) {
4493
+ fs.symlinkSync(workspacePackageDir, aliasPackageDir, process.platform === "win32" ? "junction" : "dir");
4494
+ }
4495
+ } catch (error) {
4496
+ const err = error;
4497
+ if (err.code !== "EEXIST") throw error;
4498
+ }
4499
+ const existingNodePath = env["NODE_PATH"];
4500
+ const nodePathEntries = [aliasRoot, ...existingNodePath ? existingNodePath.split(path.delimiter) : []].filter(
4501
+ Boolean
4502
+ );
4503
+ return {
4504
+ ...env,
4505
+ NODE_PATH: [...new Set(nodePathEntries)].join(path.delimiter)
4506
+ };
4507
+ }
4508
+ function findUp(startDir, testFn) {
4509
+ const path = __require("node:path");
4510
+ let currentDir = startDir;
4511
+ while (true) {
4512
+ const result = testFn(currentDir);
4513
+ if (result !== void 0) return result;
4514
+ const parentDir = path.dirname(currentDir);
4515
+ if (parentDir === currentDir) return void 0;
4516
+ currentDir = parentDir;
4517
+ }
4518
+ }
4519
+ function findWorkspaceProtocolDir(startDir) {
4520
+ const fs = __require("node:fs");
4521
+ const path = __require("node:path");
4522
+ return findUp(startDir, (dir) => {
4523
+ const candidate = path.join(dir, "libs", "protocol");
4524
+ return fs.existsSync(path.join(candidate, "dist", "index.js")) ? candidate : void 0;
4525
+ });
4526
+ }
4527
+ function findInstalledProtocolPackageDir(startDir) {
4528
+ const fs = __require("node:fs");
4529
+ const path = __require("node:path");
4530
+ return findUp(startDir, (dir) => {
4531
+ const candidate = path.join(dir, "node_modules", "@frontmcp", "protocol");
4532
+ return fs.existsSync(path.join(candidate, "package.json")) ? candidate : void 0;
4533
+ });
4534
+ }
4535
+ function findWorkspaceNodeModulesDir(startDir) {
4536
+ const fs = __require("node:fs");
4537
+ const path = __require("node:path");
4538
+ return findUp(startDir, (dir) => {
4539
+ const candidate = path.join(dir, "node_modules");
4540
+ return fs.existsSync(candidate) ? candidate : void 0;
4541
+ });
4542
+ }
4340
4543
 
4341
4544
  // libs/testing/src/assertions/mcp-assertions.ts
4342
4545
  var McpAssertions = {
@@ -4742,15 +4945,10 @@ var expect = jestExpect;
4742
4945
 
4743
4946
  // libs/testing/src/platform/platform-types.ts
4744
4947
  function getPlatformMetaNamespace(platform) {
4745
- switch (platform) {
4746
- case "openai":
4747
- return "openai";
4748
- default:
4749
- return "ui";
4750
- }
4948
+ return "ui";
4751
4949
  }
4752
4950
  function getPlatformMimeType(platform) {
4753
- return platform === "openai" ? "text/html+skybridge" : "text/html+mcp";
4951
+ return "text/html;profile=mcp-app";
4754
4952
  }
4755
4953
  function isOpenAIPlatform(platform) {
4756
4954
  return platform === "openai";
@@ -4759,31 +4957,16 @@ function isExtAppsPlatform(platform) {
4759
4957
  return platform === "ext-apps";
4760
4958
  }
4761
4959
  function isUiPlatform(platform) {
4762
- return platform !== "openai";
4960
+ return true;
4763
4961
  }
4764
4962
  function getToolsListMetaPrefixes(platform) {
4765
- switch (platform) {
4766
- case "openai":
4767
- return ["openai/"];
4768
- default:
4769
- return ["ui/"];
4770
- }
4963
+ return ["ui/"];
4771
4964
  }
4772
4965
  function getToolCallMetaPrefixes(platform) {
4773
- switch (platform) {
4774
- case "openai":
4775
- return ["openai/"];
4776
- default:
4777
- return ["ui/"];
4778
- }
4966
+ return ["ui/"];
4779
4967
  }
4780
4968
  function getForbiddenMetaPrefixes(platform) {
4781
- switch (platform) {
4782
- case "openai":
4783
- return ["ui/", "frontmcp/"];
4784
- default:
4785
- return ["openai/", "frontmcp/"];
4786
- }
4969
+ return ["openai/", "frontmcp/"];
4787
4970
  }
4788
4971
 
4789
4972
  // libs/testing/src/ui/ui-matchers.ts
@@ -4890,12 +5073,12 @@ var toHaveWidgetMetadata = function(received) {
4890
5073
  };
4891
5074
  }
4892
5075
  const hasUiHtml = Boolean(meta["ui/html"]);
4893
- const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
4894
5076
  const hasMimeType2 = Boolean(meta["ui/mimeType"]);
4895
- const pass = hasUiHtml || hasOutputTemplate || hasMimeType2;
5077
+ const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
5078
+ const pass = hasUiHtml || hasMimeType2 || hasUiObject;
4896
5079
  return {
4897
5080
  pass,
4898
- message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, openai/outputTemplate, or ui/mimeType)"
5081
+ message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)"
4899
5082
  };
4900
5083
  };
4901
5084
  var toHaveCssClass = function(received, className) {
@@ -5058,14 +5241,7 @@ var toHavePlatformMimeType = function(received, platform) {
5058
5241
  message: () => `Expected _meta to have MIME type "${expectedMimeType}" for platform "${platform}", but no _meta found`
5059
5242
  };
5060
5243
  }
5061
- let mimeTypeKey;
5062
- switch (platform) {
5063
- case "openai":
5064
- mimeTypeKey = "openai/mimeType";
5065
- break;
5066
- default:
5067
- mimeTypeKey = "ui/mimeType";
5068
- }
5244
+ const mimeTypeKey = "ui/mimeType";
5069
5245
  const actualMimeType = meta[mimeTypeKey];
5070
5246
  const pass = actualMimeType === expectedMimeType;
5071
5247
  return {
@@ -5081,14 +5257,7 @@ var toHavePlatformHtml = function(received, platform) {
5081
5257
  message: () => `Expected _meta to have platform HTML for "${platform}", but no _meta found`
5082
5258
  };
5083
5259
  }
5084
- let htmlKey;
5085
- switch (platform) {
5086
- case "openai":
5087
- htmlKey = "openai/html";
5088
- break;
5089
- default:
5090
- htmlKey = "ui/html";
5091
- }
5260
+ const htmlKey = "ui/html";
5092
5261
  const html = meta[htmlKey];
5093
5262
  const pass = typeof html === "string" && html.length > 0;
5094
5263
  return {
@@ -5952,7 +6121,7 @@ var UIAssertions = {
5952
6121
  },
5953
6122
  /**
5954
6123
  * Assert that widget metadata is present in the result.
5955
- * Checks for ui/html, openai/outputTemplate, or ui/mimeType.
6124
+ * Checks for ui/html, ui/mimeType, or nested ui object with resourceUri.
5956
6125
  * @param result - The tool result wrapper
5957
6126
  * @throws Error if widget metadata is missing
5958
6127
  */
@@ -5962,10 +6131,10 @@ var UIAssertions = {
5962
6131
  throw new Error("Expected tool result to have _meta with widget metadata");
5963
6132
  }
5964
6133
  const hasUiHtml = Boolean(meta["ui/html"]);
5965
- const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
5966
6134
  const hasMimeType2 = Boolean(meta["ui/mimeType"]);
5967
- if (!hasUiHtml && !hasOutputTemplate && !hasMimeType2) {
5968
- throw new Error("Expected _meta to have widget metadata (ui/html, openai/outputTemplate, or ui/mimeType)");
6135
+ const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
6136
+ if (!hasUiHtml && !hasMimeType2 && !hasUiObject) {
6137
+ throw new Error("Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)");
5969
6138
  }
5970
6139
  },
5971
6140
  /**
@@ -5993,7 +6162,7 @@ var UIAssertions = {
5993
6162
  // ═══════════════════════════════════════════════════════════════════
5994
6163
  /**
5995
6164
  * Assert tool result has correct meta keys for OpenAI platform.
5996
- * Verifies openai/* keys are present and ui/*, frontmcp/* keys are absent.
6165
+ * Verifies ui/* keys are present and openai/*, frontmcp/* keys are absent.
5997
6166
  * @param result - The tool result wrapper
5998
6167
  * @throws Error if meta keys don't match OpenAI expectations
5999
6168
  */
@@ -6076,14 +6245,7 @@ var UIAssertions = {
6076
6245
  if (!meta) {
6077
6246
  throw new Error(`Expected tool result to have _meta with MIME type for platform "${platform}"`);
6078
6247
  }
6079
- let mimeTypeKey;
6080
- switch (platform) {
6081
- case "openai":
6082
- mimeTypeKey = "openai/mimeType";
6083
- break;
6084
- default:
6085
- mimeTypeKey = "ui/mimeType";
6086
- }
6248
+ const mimeTypeKey = "ui/mimeType";
6087
6249
  const actualMimeType = meta[mimeTypeKey];
6088
6250
  if (actualMimeType !== expectedMimeType) {
6089
6251
  throw new Error(
@@ -6103,14 +6265,7 @@ var UIAssertions = {
6103
6265
  if (!meta) {
6104
6266
  throw new Error(`Expected tool result to have _meta with platform HTML for "${platform}"`);
6105
6267
  }
6106
- let htmlKey;
6107
- switch (platform) {
6108
- case "openai":
6109
- htmlKey = "openai/html";
6110
- break;
6111
- default:
6112
- htmlKey = "ui/html";
6113
- }
6268
+ const htmlKey = "ui/html";
6114
6269
  const html = meta[htmlKey];
6115
6270
  if (typeof html !== "string" || html.length === 0) {
6116
6271
  throw new Error(
@@ -6250,18 +6405,12 @@ function generateFullUIToolOutput(input) {
6250
6405
  timestamp: Date.now()
6251
6406
  };
6252
6407
  }
6253
- var EXPECTED_OPENAI_TOOLS_LIST_META_KEYS = [
6254
- "openai/outputTemplate",
6255
- "openai/resultCanProduceWidget",
6256
- "openai/widgetAccessible"
6257
- ];
6258
- var EXPECTED_OPENAI_TOOL_CALL_META_KEYS = ["openai/html", "openai/mimeType", "openai/type"];
6408
+ var EXPECTED_OPENAI_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
6409
+ var EXPECTED_OPENAI_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
6259
6410
  var EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
6260
6411
  var EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
6261
6412
  var EXPECTED_GENERIC_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
6262
6413
  var EXPECTED_GENERIC_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
6263
- var EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS = EXPECTED_GENERIC_TOOLS_LIST_META_KEYS;
6264
- var EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS = EXPECTED_GENERIC_TOOL_CALL_META_KEYS;
6265
6414
 
6266
6415
  // libs/testing/src/perf/metrics-collector.ts
6267
6416
  function isGcAvailable() {
@@ -7806,6 +7955,10 @@ async function saveReports(measurements, outputDir, baseline, gitInfo) {
7806
7955
  function createReportGenerator() {
7807
7956
  return new ReportGenerator();
7808
7957
  }
7958
+
7959
+ // libs/testing/src/raw-client/index.ts
7960
+ import { Client } from "@frontmcp/protocol";
7961
+ import { StdioClientTransport } from "@frontmcp/protocol";
7809
7962
  export {
7810
7963
  AssertionError,
7811
7964
  AuthHeaders,
@@ -7816,14 +7969,16 @@ export {
7816
7969
  DefaultMockRegistry,
7817
7970
  EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS,
7818
7971
  EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS,
7819
- EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS,
7820
- EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS,
7972
+ EXPECTED_GENERIC_TOOLS_LIST_META_KEYS,
7973
+ EXPECTED_GENERIC_TOOL_CALL_META_KEYS,
7821
7974
  EXPECTED_OPENAI_TOOLS_LIST_META_KEYS,
7822
7975
  EXPECTED_OPENAI_TOOL_CALL_META_KEYS,
7823
7976
  FULL_UI_TOOL_CONFIG,
7824
7977
  LeakDetector,
7825
7978
  McpAssertions,
7979
+ Client as McpClient,
7826
7980
  McpProtocolError,
7981
+ StdioClientTransport as McpStdioClientTransport,
7827
7982
  McpTestClient,
7828
7983
  McpTestClientBuilder,
7829
7984
  MetricsCollector,
@@ -1,22 +1,12 @@
1
1
  // libs/testing/src/platform/platform-types.ts
2
2
  function getPlatformMimeType(platform) {
3
- return platform === "openai" ? "text/html+skybridge" : "text/html+mcp";
3
+ return "text/html;profile=mcp-app";
4
4
  }
5
5
  function getToolCallMetaPrefixes(platform) {
6
- switch (platform) {
7
- case "openai":
8
- return ["openai/"];
9
- default:
10
- return ["ui/"];
11
- }
6
+ return ["ui/"];
12
7
  }
13
8
  function getForbiddenMetaPrefixes(platform) {
14
- switch (platform) {
15
- case "openai":
16
- return ["ui/", "frontmcp/"];
17
- default:
18
- return ["openai/", "frontmcp/"];
19
- }
9
+ return ["openai/", "frontmcp/"];
20
10
  }
21
11
 
22
12
  // libs/testing/src/ui/ui-matchers.ts
@@ -123,12 +113,12 @@ var toHaveWidgetMetadata = function(received) {
123
113
  };
124
114
  }
125
115
  const hasUiHtml = Boolean(meta["ui/html"]);
126
- const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
127
116
  const hasMimeType = Boolean(meta["ui/mimeType"]);
128
- const pass = hasUiHtml || hasOutputTemplate || hasMimeType;
117
+ const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
118
+ const pass = hasUiHtml || hasMimeType || hasUiObject;
129
119
  return {
130
120
  pass,
131
- message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, openai/outputTemplate, or ui/mimeType)"
121
+ message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)"
132
122
  };
133
123
  };
134
124
  var toHaveCssClass = function(received, className) {
@@ -291,14 +281,7 @@ var toHavePlatformMimeType = function(received, platform) {
291
281
  message: () => `Expected _meta to have MIME type "${expectedMimeType}" for platform "${platform}", but no _meta found`
292
282
  };
293
283
  }
294
- let mimeTypeKey;
295
- switch (platform) {
296
- case "openai":
297
- mimeTypeKey = "openai/mimeType";
298
- break;
299
- default:
300
- mimeTypeKey = "ui/mimeType";
301
- }
284
+ const mimeTypeKey = "ui/mimeType";
302
285
  const actualMimeType = meta[mimeTypeKey];
303
286
  const pass = actualMimeType === expectedMimeType;
304
287
  return {
@@ -314,14 +297,7 @@ var toHavePlatformHtml = function(received, platform) {
314
297
  message: () => `Expected _meta to have platform HTML for "${platform}", but no _meta found`
315
298
  };
316
299
  }
317
- let htmlKey;
318
- switch (platform) {
319
- case "openai":
320
- htmlKey = "openai/html";
321
- break;
322
- default:
323
- htmlKey = "ui/html";
324
- }
300
+ const htmlKey = "ui/html";
325
301
  const html = meta[htmlKey];
326
302
  const pass = typeof html === "string" && html.length > 0;
327
303
  return {