@frontmcp/testing 0.12.2 → 1.0.0-beta.10

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 (43) 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 +255 -36
  9. package/esm/index.mjs +291 -122
  10. package/esm/matchers/index.mjs +8 -32
  11. package/esm/package.json +6 -6
  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 +241 -36
  19. package/fixtures/test-fixture.d.ts.map +1 -1
  20. package/index.d.ts +2 -1
  21. package/index.d.ts.map +1 -1
  22. package/index.js +283 -126
  23. package/matchers/index.js +8 -32
  24. package/package.json +6 -6
  25. package/perf/index.js +232 -36
  26. package/platform/platform-client-info.d.ts +2 -1
  27. package/platform/platform-client-info.d.ts.map +1 -1
  28. package/platform/platform-types.d.ts +11 -16
  29. package/platform/platform-types.d.ts.map +1 -1
  30. package/playwright/index.d.ts +1 -1
  31. package/raw-client/index.d.ts +7 -0
  32. package/raw-client/index.d.ts.map +1 -0
  33. package/server/index.d.ts +1 -1
  34. package/server/index.d.ts.map +1 -1
  35. package/server/port-registry.d.ts +24 -6
  36. package/server/port-registry.d.ts.map +1 -1
  37. package/server/test-server.d.ts +1 -1
  38. package/server/test-server.d.ts.map +1 -1
  39. package/setup.js +8 -32
  40. package/ui/ui-assertions.d.ts +3 -4
  41. package/ui/ui-assertions.d.ts.map +1 -1
  42. package/ui/ui-matchers.d.ts +1 -2
  43. package/ui/ui-matchers.d.ts.map +1 -1
package/index.js CHANGED
@@ -39,14 +39,16 @@ __export(index_exports, {
39
39
  DefaultMockRegistry: () => DefaultMockRegistry,
40
40
  EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS: () => EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS,
41
41
  EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS: () => EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS,
42
- EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS: () => EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS,
43
- EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS: () => EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS,
42
+ EXPECTED_GENERIC_TOOLS_LIST_META_KEYS: () => EXPECTED_GENERIC_TOOLS_LIST_META_KEYS,
43
+ EXPECTED_GENERIC_TOOL_CALL_META_KEYS: () => EXPECTED_GENERIC_TOOL_CALL_META_KEYS,
44
44
  EXPECTED_OPENAI_TOOLS_LIST_META_KEYS: () => EXPECTED_OPENAI_TOOLS_LIST_META_KEYS,
45
45
  EXPECTED_OPENAI_TOOL_CALL_META_KEYS: () => EXPECTED_OPENAI_TOOL_CALL_META_KEYS,
46
46
  FULL_UI_TOOL_CONFIG: () => FULL_UI_TOOL_CONFIG,
47
47
  LeakDetector: () => LeakDetector,
48
48
  McpAssertions: () => McpAssertions,
49
+ McpClient: () => import_protocol.Client,
49
50
  McpProtocolError: () => McpProtocolError,
51
+ McpStdioClientTransport: () => import_protocol2.StdioClientTransport,
50
52
  McpTestClient: () => McpTestClient,
51
53
  McpTestClientBuilder: () => McpTestClientBuilder,
52
54
  MetricsCollector: () => MetricsCollector,
@@ -206,7 +208,7 @@ function getPlatformCapabilities(platform) {
206
208
  ...baseCapabilities,
207
209
  experimental: {
208
210
  [MCP_APPS_EXTENSION_KEY]: {
209
- mimeTypes: ["text/html+mcp"]
211
+ mimeTypes: ["text/html;profile=mcp-app"]
210
212
  }
211
213
  }
212
214
  };
@@ -327,7 +329,7 @@ var McpTestClientBuilder = class {
327
329
  * .withCapabilities({
328
330
  * sampling: {},
329
331
  * experimental: {
330
- * 'io.modelcontextprotocol/ui': { mimeTypes: ['text/html+mcp'] }
332
+ * 'io.modelcontextprotocol/ui': { mimeTypes: ['text/html;profile=mcp-app'] }
331
333
  * }
332
334
  * })
333
335
  * .buildAndConnect();
@@ -1373,11 +1375,24 @@ var McpTestClient = class {
1373
1375
  this.log("debug", `Connecting to ${this.config.baseUrl}...`);
1374
1376
  this.transport = this.createTransport();
1375
1377
  await this.transport.connect();
1376
- const initResponse = await this.initialize();
1377
- if (!initResponse.success || !initResponse.data) {
1378
- throw new Error(`Failed to initialize MCP connection: ${initResponse.error?.message ?? "Unknown error"}`);
1378
+ const maxRetries = 3;
1379
+ const retryDelayMs = 500;
1380
+ let lastError;
1381
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1382
+ const initResponse = await this.initialize();
1383
+ if (initResponse.success && initResponse.data) {
1384
+ this.initResult = initResponse.data;
1385
+ break;
1386
+ }
1387
+ lastError = initResponse.error?.message ?? "Unknown error";
1388
+ if (attempt < maxRetries) {
1389
+ this.log("debug", `MCP init attempt ${attempt} failed (${lastError}), retrying in ${retryDelayMs}ms...`);
1390
+ await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
1391
+ }
1392
+ }
1393
+ if (!this.initResult) {
1394
+ throw new Error(`Failed to initialize MCP connection after ${maxRetries} attempts: ${lastError}`);
1379
1395
  }
1380
- this.initResult = initResponse.data;
1381
1396
  this._sessionId = this.transport.getSessionId();
1382
1397
  this._sessionInfo = {
1383
1398
  id: this._sessionId ?? `session-${Date.now()}`,
@@ -2068,7 +2083,7 @@ var McpTestClient = class {
2068
2083
  const raw = response.data ?? { content: [] };
2069
2084
  const isError2 = !response.success || raw.isError === true;
2070
2085
  const meta = raw._meta;
2071
- const hasUI = meta?.["ui/html"] !== void 0 || meta?.["ui/component"] !== void 0 || meta?.["openai/html"] !== void 0 || meta?.["frontmcp/html"] !== void 0;
2086
+ const hasUI = meta?.["ui/html"] !== void 0 || meta?.["ui/component"] !== void 0;
2072
2087
  const structuredContent = raw["structuredContent"];
2073
2088
  return {
2074
2089
  raw,
@@ -3922,6 +3937,7 @@ var MockCimdServer = class {
3922
3937
 
3923
3938
  // libs/testing/src/server/test-server.ts
3924
3939
  var import_child_process = require("child_process");
3940
+ var import_utils = require("@frontmcp/utils");
3925
3941
 
3926
3942
  // libs/testing/src/errors/index.ts
3927
3943
  var TestClientError = class extends Error {
@@ -4001,9 +4017,16 @@ var E2E_PORT_RANGES = {
4001
4017
  "demo-e2e-agents": { start: 50270, size: 10 },
4002
4018
  "demo-e2e-transport-recreation": { start: 50280, size: 10 },
4003
4019
  "demo-e2e-jobs": { start: 50290, size: 10 },
4004
- // Infrastructure E2E tests (50300-50399)
4020
+ // Infrastructure E2E tests (50300-50409)
4005
4021
  "demo-e2e-redis": { start: 50300, size: 10 },
4006
4022
  "demo-e2e-serverless": { start: 50310, size: 10 },
4023
+ "demo-e2e-uipack": { start: 50320, size: 10 },
4024
+ "demo-e2e-agent-adapters": { start: 50330, size: 10 },
4025
+ "demo-e2e-guard": { start: 50340, size: 10 },
4026
+ // ESM E2E tests (50400-50449)
4027
+ "esm-package-server": { start: 50400, size: 10 },
4028
+ "esm-package-server-hot-reload": { start: 50410, size: 10 },
4029
+ "esm-package-server-cli": { start: 50420, size: 10 },
4007
4030
  // Mock servers and utilities (50900-50999)
4008
4031
  "mock-oauth": { start: 50900, size: 10 },
4009
4032
  "mock-api": { start: 50910, size: 10 },
@@ -4075,7 +4098,7 @@ async function tryReservePort(port, project) {
4075
4098
  server.once("error", () => {
4076
4099
  resolve(false);
4077
4100
  });
4078
- server.listen(port, "::", () => {
4101
+ server.listen(port, () => {
4079
4102
  reservedPorts.set(port, {
4080
4103
  port,
4081
4104
  project,
@@ -4116,7 +4139,7 @@ async function isPortAvailable(port) {
4116
4139
  server.once("error", () => {
4117
4140
  resolve(false);
4118
4141
  });
4119
- server.listen(port, "::", () => {
4142
+ server.listen(port, () => {
4120
4143
  server.close(() => {
4121
4144
  resolve(true);
4122
4145
  });
@@ -4154,15 +4177,37 @@ var TestServer = class _TestServer {
4154
4177
  */
4155
4178
  static async start(options) {
4156
4179
  const project = options.project ?? "default";
4157
- const { port, release } = await reservePort(project, options.port);
4158
- const server = new _TestServer(options, port, release);
4159
- try {
4160
- await server.startProcess();
4161
- } catch (error) {
4162
- await server.stop();
4163
- throw error;
4180
+ const maxAttempts = 3;
4181
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4182
+ const { port, release } = await reservePort(project, options.port);
4183
+ const server = new _TestServer(options, port, release);
4184
+ try {
4185
+ await server.startProcess();
4186
+ return server;
4187
+ } catch (error) {
4188
+ try {
4189
+ await server.stop();
4190
+ } catch (cleanupError) {
4191
+ if (options.debug || DEBUG_SERVER) {
4192
+ const msg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
4193
+ console.warn(`[TestServer] Cleanup failed after startup error: ${msg}`);
4194
+ }
4195
+ }
4196
+ const isEADDRINUSE = error instanceof Error && (error.message.includes("EADDRINUSE") || server.getLogs().some((l) => l.includes("EADDRINUSE")));
4197
+ if (isEADDRINUSE && attempt < maxAttempts) {
4198
+ const delayMs = attempt * 500;
4199
+ if (options.debug || DEBUG_SERVER) {
4200
+ console.warn(
4201
+ `[TestServer] EADDRINUSE on port ${port}, retrying in ${delayMs}ms (attempt ${attempt}/${maxAttempts})`
4202
+ );
4203
+ }
4204
+ await sleep2(delayMs);
4205
+ continue;
4206
+ }
4207
+ throw error;
4208
+ }
4164
4209
  }
4165
- return server;
4210
+ throw new Error(`[TestServer] Failed to start after ${maxAttempts} attempts`);
4166
4211
  }
4167
4212
  /**
4168
4213
  * Start an Nx project as test server
@@ -4173,22 +4218,44 @@ var TestServer = class _TestServer {
4173
4218
  `Invalid project name: ${project}. Must contain only alphanumeric, underscore, and hyphen characters.`
4174
4219
  );
4175
4220
  }
4176
- const { port, release } = await reservePort(project, options.port);
4177
- const serverOptions = {
4178
- ...options,
4179
- port,
4180
- project,
4181
- command: `npx nx serve ${project} --port ${port}`,
4182
- cwd: options.cwd ?? process.cwd()
4183
- };
4184
- const server = new _TestServer(serverOptions, port, release);
4185
- try {
4186
- await server.startProcess();
4187
- } catch (error) {
4188
- await server.stop();
4189
- throw error;
4221
+ const maxAttempts = 3;
4222
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
4223
+ const { port, release } = await reservePort(project, options.port);
4224
+ const serverOptions = {
4225
+ ...options,
4226
+ port,
4227
+ project,
4228
+ command: `npx nx serve ${project} --port ${port}`,
4229
+ cwd: options.cwd ?? process.cwd()
4230
+ };
4231
+ const server = new _TestServer(serverOptions, port, release);
4232
+ try {
4233
+ await server.startProcess();
4234
+ return server;
4235
+ } catch (error) {
4236
+ try {
4237
+ await server.stop();
4238
+ } catch (cleanupError) {
4239
+ if (options.debug || DEBUG_SERVER) {
4240
+ const msg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
4241
+ console.warn(`[TestServer] Cleanup failed after startup error: ${msg}`);
4242
+ }
4243
+ }
4244
+ const isEADDRINUSE = error instanceof Error && (error.message.includes("EADDRINUSE") || server.getLogs().some((l) => l.includes("EADDRINUSE")));
4245
+ if (isEADDRINUSE && attempt < maxAttempts) {
4246
+ const delayMs = attempt * 500;
4247
+ if (options.debug || DEBUG_SERVER) {
4248
+ console.warn(
4249
+ `[TestServer] EADDRINUSE on port ${port}, retrying in ${delayMs}ms (attempt ${attempt}/${maxAttempts})`
4250
+ );
4251
+ }
4252
+ await sleep2(delayMs);
4253
+ continue;
4254
+ }
4255
+ throw error;
4256
+ }
4190
4257
  }
4191
- return server;
4258
+ throw new Error(`[TestServer] Failed to start after ${maxAttempts} attempts`);
4192
4259
  }
4193
4260
  /**
4194
4261
  * Create a test server connected to an already running server
@@ -4225,7 +4292,21 @@ var TestServer = class _TestServer {
4225
4292
  }
4226
4293
  if (this.process) {
4227
4294
  this.log("Stopping server...");
4228
- this.process.kill("SIGTERM");
4295
+ if (this.process.exitCode !== null || this.process.signalCode !== null) {
4296
+ this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
4297
+ this.process = null;
4298
+ return;
4299
+ }
4300
+ const pid = this.process.pid;
4301
+ try {
4302
+ if (pid !== void 0) {
4303
+ process.kill(-pid, "SIGTERM");
4304
+ } else {
4305
+ this.process.kill("SIGTERM");
4306
+ }
4307
+ } catch {
4308
+ this.process.kill("SIGTERM");
4309
+ }
4229
4310
  const exitPromise = new Promise((resolve) => {
4230
4311
  if (this.process) {
4231
4312
  this.process.once("exit", () => resolve());
@@ -4236,7 +4317,16 @@ var TestServer = class _TestServer {
4236
4317
  const killTimeout = setTimeout(() => {
4237
4318
  if (this.process) {
4238
4319
  this.log("Force killing server after timeout...");
4239
- this.process.kill("SIGKILL");
4320
+ const killPid = this.process.pid;
4321
+ try {
4322
+ if (killPid !== void 0) {
4323
+ process.kill(-killPid, "SIGKILL");
4324
+ } else {
4325
+ this.process.kill("SIGKILL");
4326
+ }
4327
+ } catch {
4328
+ this.process.kill("SIGKILL");
4329
+ }
4240
4330
  }
4241
4331
  }, 5e3);
4242
4332
  await exitPromise;
@@ -4301,14 +4391,17 @@ var TestServer = class _TestServer {
4301
4391
  ...this.options.env,
4302
4392
  PORT: String(this.options.port)
4303
4393
  };
4394
+ const runtimeEnv = withWorkspaceProtocolFallback(env, this.options.cwd);
4304
4395
  if (this.portRelease) {
4305
4396
  await this.portRelease();
4306
4397
  this.portRelease = null;
4398
+ await sleep2(300);
4307
4399
  }
4308
4400
  this.process = (0, import_child_process.spawn)(this.options.command, [], {
4309
4401
  cwd: this.options.cwd,
4310
- env,
4402
+ env: runtimeEnv,
4311
4403
  shell: true,
4404
+ detached: true,
4312
4405
  stdio: ["pipe", "pipe", "pipe"]
4313
4406
  });
4314
4407
  if (this.process.pid !== void 0) {
@@ -4460,6 +4553,111 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
4460
4553
  function sleep2(ms) {
4461
4554
  return new Promise((resolve) => setTimeout(resolve, ms));
4462
4555
  }
4556
+ function withWorkspaceProtocolFallback(env, cwd) {
4557
+ if (findInstalledProtocolPackageDir(cwd)) {
4558
+ return env;
4559
+ }
4560
+ try {
4561
+ const workspacePackageDir = findWorkspaceProtocolDir(cwd);
4562
+ if (!workspacePackageDir) {
4563
+ return env;
4564
+ }
4565
+ ensureWorkspaceProtocolLink(cwd, workspacePackageDir);
4566
+ if (findInstalledProtocolPackageDir(cwd)) {
4567
+ return env;
4568
+ }
4569
+ return withProtocolNodePathAlias(env, cwd, workspacePackageDir);
4570
+ } catch (err) {
4571
+ if (DEBUG_SERVER) {
4572
+ console.error(
4573
+ `[TestServer] Workspace protocol fallback failed: ${err instanceof Error ? err.message : String(err)}`
4574
+ );
4575
+ }
4576
+ return env;
4577
+ }
4578
+ }
4579
+ function ensureWorkspaceProtocolLink(cwd, workspacePackageDir) {
4580
+ const fs = require("node:fs");
4581
+ const path = require("node:path");
4582
+ const nodeModulesDir = findWorkspaceNodeModulesDir(cwd);
4583
+ if (!nodeModulesDir) {
4584
+ return;
4585
+ }
4586
+ const scopeDir = path.join(nodeModulesDir, "@frontmcp");
4587
+ const aliasPackageDir = path.join(scopeDir, "protocol");
4588
+ if (fs.existsSync(aliasPackageDir)) {
4589
+ return;
4590
+ }
4591
+ fs.mkdirSync(scopeDir, { recursive: true });
4592
+ try {
4593
+ fs.symlinkSync(workspacePackageDir, aliasPackageDir, process.platform === "win32" ? "junction" : "dir");
4594
+ } catch (error) {
4595
+ const err = error;
4596
+ if (err.code !== "EEXIST") {
4597
+ throw error;
4598
+ }
4599
+ }
4600
+ }
4601
+ function withProtocolNodePathAlias(env, cwd, workspacePackageDir) {
4602
+ const fs = require("node:fs");
4603
+ const os = require("node:os");
4604
+ const path = require("node:path");
4605
+ const aliasRoot = path.join(os.tmpdir(), "frontmcp-test-node-path", (0, import_utils.sha256Hex)(cwd).slice(0, 12));
4606
+ const scopeDir = path.join(aliasRoot, "@frontmcp");
4607
+ const aliasPackageDir = path.join(scopeDir, "protocol");
4608
+ fs.mkdirSync(scopeDir, { recursive: true });
4609
+ try {
4610
+ if (!fs.existsSync(aliasPackageDir)) {
4611
+ fs.symlinkSync(workspacePackageDir, aliasPackageDir, process.platform === "win32" ? "junction" : "dir");
4612
+ }
4613
+ } catch (error) {
4614
+ const err = error;
4615
+ if (err.code !== "EEXIST") throw error;
4616
+ }
4617
+ const existingNodePath = env["NODE_PATH"];
4618
+ const nodePathEntries = [aliasRoot, ...existingNodePath ? existingNodePath.split(path.delimiter) : []].filter(
4619
+ Boolean
4620
+ );
4621
+ return {
4622
+ ...env,
4623
+ NODE_PATH: [...new Set(nodePathEntries)].join(path.delimiter)
4624
+ };
4625
+ }
4626
+ function findUp(startDir, testFn) {
4627
+ const path = require("node:path");
4628
+ let currentDir = startDir;
4629
+ while (true) {
4630
+ const result = testFn(currentDir);
4631
+ if (result !== void 0) return result;
4632
+ const parentDir = path.dirname(currentDir);
4633
+ if (parentDir === currentDir) return void 0;
4634
+ currentDir = parentDir;
4635
+ }
4636
+ }
4637
+ function findWorkspaceProtocolDir(startDir) {
4638
+ const fs = require("node:fs");
4639
+ const path = require("node:path");
4640
+ return findUp(startDir, (dir) => {
4641
+ const candidate = path.join(dir, "libs", "protocol");
4642
+ return fs.existsSync(path.join(candidate, "dist", "index.js")) ? candidate : void 0;
4643
+ });
4644
+ }
4645
+ function findInstalledProtocolPackageDir(startDir) {
4646
+ const fs = require("node:fs");
4647
+ const path = require("node:path");
4648
+ return findUp(startDir, (dir) => {
4649
+ const candidate = path.join(dir, "node_modules", "@frontmcp", "protocol");
4650
+ return fs.existsSync(path.join(candidate, "package.json")) ? candidate : void 0;
4651
+ });
4652
+ }
4653
+ function findWorkspaceNodeModulesDir(startDir) {
4654
+ const fs = require("node:fs");
4655
+ const path = require("node:path");
4656
+ return findUp(startDir, (dir) => {
4657
+ const candidate = path.join(dir, "node_modules");
4658
+ return fs.existsSync(candidate) ? candidate : void 0;
4659
+ });
4660
+ }
4463
4661
 
4464
4662
  // libs/testing/src/assertions/mcp-assertions.ts
4465
4663
  var McpAssertions = {
@@ -4627,6 +4825,13 @@ function hasMimeType(result, mimeType) {
4627
4825
  }
4628
4826
 
4629
4827
  // libs/testing/src/fixtures/test-fixture.ts
4828
+ var import_globals = require("@jest/globals");
4829
+ var describe2 = import_globals.describe;
4830
+ var beforeAll2 = import_globals.beforeAll;
4831
+ var beforeEach2 = import_globals.beforeEach;
4832
+ var afterEach2 = import_globals.afterEach;
4833
+ var afterAll2 = import_globals.afterAll;
4834
+ var it2 = import_globals.it;
4630
4835
  var currentConfig = {};
4631
4836
  var serverInstance = null;
4632
4837
  var tokenFactory = null;
@@ -4798,7 +5003,7 @@ function resolveServerCommand(server) {
4798
5003
  return `npx tsx ${server}`;
4799
5004
  }
4800
5005
  function testWithFixtures(name, fn) {
4801
- it(name, async () => {
5006
+ it2(name, async () => {
4802
5007
  const fixtures = await createTestFixtures();
4803
5008
  let testFailed = false;
4804
5009
  try {
@@ -4813,12 +5018,12 @@ function testWithFixtures(name, fn) {
4813
5018
  }
4814
5019
  function use(config) {
4815
5020
  currentConfig = { ...currentConfig, ...config };
4816
- afterAll(async () => {
5021
+ afterAll2(async () => {
4817
5022
  await cleanupSharedResources();
4818
5023
  });
4819
5024
  }
4820
5025
  function skip(name, fn) {
4821
- it.skip(name, async () => {
5026
+ it2.skip(name, async () => {
4822
5027
  const fixtures = await createTestFixtures();
4823
5028
  let testFailed = false;
4824
5029
  try {
@@ -4832,7 +5037,7 @@ function skip(name, fn) {
4832
5037
  });
4833
5038
  }
4834
5039
  function only(name, fn) {
4835
- it.only(name, async () => {
5040
+ it2.only(name, async () => {
4836
5041
  const fixtures = await createTestFixtures();
4837
5042
  let testFailed = false;
4838
5043
  try {
@@ -4846,34 +5051,29 @@ function only(name, fn) {
4846
5051
  });
4847
5052
  }
4848
5053
  function todo(name) {
4849
- it.todo(name);
5054
+ it2.todo(name);
4850
5055
  }
4851
5056
  var test = testWithFixtures;
4852
5057
  test.use = use;
4853
- test.describe = describe;
4854
- test.beforeAll = beforeAll;
4855
- test.beforeEach = beforeEach;
4856
- test.afterEach = afterEach;
4857
- test.afterAll = afterAll;
5058
+ test.describe = describe2;
5059
+ test.beforeAll = beforeAll2;
5060
+ test.beforeEach = beforeEach2;
5061
+ test.afterEach = afterEach2;
5062
+ test.afterAll = afterAll2;
4858
5063
  test.skip = skip;
4859
5064
  test.only = only;
4860
5065
  test.todo = todo;
4861
5066
 
4862
5067
  // libs/testing/src/expect.ts
4863
- var import_globals = require("@jest/globals");
4864
- var expect = import_globals.expect;
5068
+ var import_globals2 = require("@jest/globals");
5069
+ var expect = import_globals2.expect;
4865
5070
 
4866
5071
  // libs/testing/src/platform/platform-types.ts
4867
5072
  function getPlatformMetaNamespace(platform) {
4868
- switch (platform) {
4869
- case "openai":
4870
- return "openai";
4871
- default:
4872
- return "ui";
4873
- }
5073
+ return "ui";
4874
5074
  }
4875
5075
  function getPlatformMimeType(platform) {
4876
- return platform === "openai" ? "text/html+skybridge" : "text/html+mcp";
5076
+ return "text/html;profile=mcp-app";
4877
5077
  }
4878
5078
  function isOpenAIPlatform(platform) {
4879
5079
  return platform === "openai";
@@ -4882,31 +5082,16 @@ function isExtAppsPlatform(platform) {
4882
5082
  return platform === "ext-apps";
4883
5083
  }
4884
5084
  function isUiPlatform(platform) {
4885
- return platform !== "openai";
5085
+ return true;
4886
5086
  }
4887
5087
  function getToolsListMetaPrefixes(platform) {
4888
- switch (platform) {
4889
- case "openai":
4890
- return ["openai/"];
4891
- default:
4892
- return ["ui/"];
4893
- }
5088
+ return ["ui/"];
4894
5089
  }
4895
5090
  function getToolCallMetaPrefixes(platform) {
4896
- switch (platform) {
4897
- case "openai":
4898
- return ["openai/"];
4899
- default:
4900
- return ["ui/"];
4901
- }
5091
+ return ["ui/"];
4902
5092
  }
4903
5093
  function getForbiddenMetaPrefixes(platform) {
4904
- switch (platform) {
4905
- case "openai":
4906
- return ["ui/", "frontmcp/"];
4907
- default:
4908
- return ["openai/", "frontmcp/"];
4909
- }
5094
+ return ["openai/", "frontmcp/"];
4910
5095
  }
4911
5096
 
4912
5097
  // libs/testing/src/ui/ui-matchers.ts
@@ -5013,12 +5198,12 @@ var toHaveWidgetMetadata = function(received) {
5013
5198
  };
5014
5199
  }
5015
5200
  const hasUiHtml = Boolean(meta["ui/html"]);
5016
- const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
5017
5201
  const hasMimeType2 = Boolean(meta["ui/mimeType"]);
5018
- const pass = hasUiHtml || hasOutputTemplate || hasMimeType2;
5202
+ const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
5203
+ const pass = hasUiHtml || hasMimeType2 || hasUiObject;
5019
5204
  return {
5020
5205
  pass,
5021
- message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, openai/outputTemplate, or ui/mimeType)"
5206
+ message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)"
5022
5207
  };
5023
5208
  };
5024
5209
  var toHaveCssClass = function(received, className) {
@@ -5181,14 +5366,7 @@ var toHavePlatformMimeType = function(received, platform) {
5181
5366
  message: () => `Expected _meta to have MIME type "${expectedMimeType}" for platform "${platform}", but no _meta found`
5182
5367
  };
5183
5368
  }
5184
- let mimeTypeKey;
5185
- switch (platform) {
5186
- case "openai":
5187
- mimeTypeKey = "openai/mimeType";
5188
- break;
5189
- default:
5190
- mimeTypeKey = "ui/mimeType";
5191
- }
5369
+ const mimeTypeKey = "ui/mimeType";
5192
5370
  const actualMimeType = meta[mimeTypeKey];
5193
5371
  const pass = actualMimeType === expectedMimeType;
5194
5372
  return {
@@ -5204,14 +5382,7 @@ var toHavePlatformHtml = function(received, platform) {
5204
5382
  message: () => `Expected _meta to have platform HTML for "${platform}", but no _meta found`
5205
5383
  };
5206
5384
  }
5207
- let htmlKey;
5208
- switch (platform) {
5209
- case "openai":
5210
- htmlKey = "openai/html";
5211
- break;
5212
- default:
5213
- htmlKey = "ui/html";
5214
- }
5385
+ const htmlKey = "ui/html";
5215
5386
  const html = meta[htmlKey];
5216
5387
  const pass = typeof html === "string" && html.length > 0;
5217
5388
  return {
@@ -6075,7 +6246,7 @@ var UIAssertions = {
6075
6246
  },
6076
6247
  /**
6077
6248
  * Assert that widget metadata is present in the result.
6078
- * Checks for ui/html, openai/outputTemplate, or ui/mimeType.
6249
+ * Checks for ui/html, ui/mimeType, or nested ui object with resourceUri.
6079
6250
  * @param result - The tool result wrapper
6080
6251
  * @throws Error if widget metadata is missing
6081
6252
  */
@@ -6085,10 +6256,10 @@ var UIAssertions = {
6085
6256
  throw new Error("Expected tool result to have _meta with widget metadata");
6086
6257
  }
6087
6258
  const hasUiHtml = Boolean(meta["ui/html"]);
6088
- const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
6089
6259
  const hasMimeType2 = Boolean(meta["ui/mimeType"]);
6090
- if (!hasUiHtml && !hasOutputTemplate && !hasMimeType2) {
6091
- throw new Error("Expected _meta to have widget metadata (ui/html, openai/outputTemplate, or ui/mimeType)");
6260
+ const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
6261
+ if (!hasUiHtml && !hasMimeType2 && !hasUiObject) {
6262
+ throw new Error("Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)");
6092
6263
  }
6093
6264
  },
6094
6265
  /**
@@ -6116,7 +6287,7 @@ var UIAssertions = {
6116
6287
  // ═══════════════════════════════════════════════════════════════════
6117
6288
  /**
6118
6289
  * Assert tool result has correct meta keys for OpenAI platform.
6119
- * Verifies openai/* keys are present and ui/*, frontmcp/* keys are absent.
6290
+ * Verifies ui/* keys are present and openai/*, frontmcp/* keys are absent.
6120
6291
  * @param result - The tool result wrapper
6121
6292
  * @throws Error if meta keys don't match OpenAI expectations
6122
6293
  */
@@ -6199,14 +6370,7 @@ var UIAssertions = {
6199
6370
  if (!meta) {
6200
6371
  throw new Error(`Expected tool result to have _meta with MIME type for platform "${platform}"`);
6201
6372
  }
6202
- let mimeTypeKey;
6203
- switch (platform) {
6204
- case "openai":
6205
- mimeTypeKey = "openai/mimeType";
6206
- break;
6207
- default:
6208
- mimeTypeKey = "ui/mimeType";
6209
- }
6373
+ const mimeTypeKey = "ui/mimeType";
6210
6374
  const actualMimeType = meta[mimeTypeKey];
6211
6375
  if (actualMimeType !== expectedMimeType) {
6212
6376
  throw new Error(
@@ -6226,14 +6390,7 @@ var UIAssertions = {
6226
6390
  if (!meta) {
6227
6391
  throw new Error(`Expected tool result to have _meta with platform HTML for "${platform}"`);
6228
6392
  }
6229
- let htmlKey;
6230
- switch (platform) {
6231
- case "openai":
6232
- htmlKey = "openai/html";
6233
- break;
6234
- default:
6235
- htmlKey = "ui/html";
6236
- }
6393
+ const htmlKey = "ui/html";
6237
6394
  const html = meta[htmlKey];
6238
6395
  if (typeof html !== "string" || html.length === 0) {
6239
6396
  throw new Error(
@@ -6373,18 +6530,12 @@ function generateFullUIToolOutput(input) {
6373
6530
  timestamp: Date.now()
6374
6531
  };
6375
6532
  }
6376
- var EXPECTED_OPENAI_TOOLS_LIST_META_KEYS = [
6377
- "openai/outputTemplate",
6378
- "openai/resultCanProduceWidget",
6379
- "openai/widgetAccessible"
6380
- ];
6381
- var EXPECTED_OPENAI_TOOL_CALL_META_KEYS = ["openai/html", "openai/mimeType", "openai/type"];
6533
+ var EXPECTED_OPENAI_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
6534
+ var EXPECTED_OPENAI_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
6382
6535
  var EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
6383
6536
  var EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
6384
6537
  var EXPECTED_GENERIC_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
6385
6538
  var EXPECTED_GENERIC_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
6386
- var EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS = EXPECTED_GENERIC_TOOLS_LIST_META_KEYS;
6387
- var EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS = EXPECTED_GENERIC_TOOL_CALL_META_KEYS;
6388
6539
 
6389
6540
  // libs/testing/src/perf/metrics-collector.ts
6390
6541
  function isGcAvailable() {
@@ -7929,6 +8080,10 @@ async function saveReports(measurements, outputDir, baseline, gitInfo) {
7929
8080
  function createReportGenerator() {
7930
8081
  return new ReportGenerator();
7931
8082
  }
8083
+
8084
+ // libs/testing/src/raw-client/index.ts
8085
+ var import_protocol = require("@frontmcp/protocol");
8086
+ var import_protocol2 = require("@frontmcp/protocol");
7932
8087
  // Annotate the CommonJS export names for ESM import in node:
7933
8088
  0 && (module.exports = {
7934
8089
  AssertionError,
@@ -7940,14 +8095,16 @@ function createReportGenerator() {
7940
8095
  DefaultMockRegistry,
7941
8096
  EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS,
7942
8097
  EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS,
7943
- EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS,
7944
- EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS,
8098
+ EXPECTED_GENERIC_TOOLS_LIST_META_KEYS,
8099
+ EXPECTED_GENERIC_TOOL_CALL_META_KEYS,
7945
8100
  EXPECTED_OPENAI_TOOLS_LIST_META_KEYS,
7946
8101
  EXPECTED_OPENAI_TOOL_CALL_META_KEYS,
7947
8102
  FULL_UI_TOOL_CONFIG,
7948
8103
  LeakDetector,
7949
8104
  McpAssertions,
8105
+ McpClient,
7950
8106
  McpProtocolError,
8107
+ McpStdioClientTransport,
7951
8108
  McpTestClient,
7952
8109
  McpTestClientBuilder,
7953
8110
  MetricsCollector,