@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.
- package/assertions/mcp-assertions.d.ts +1 -1
- package/assertions/mcp-assertions.d.ts.map +1 -1
- package/client/mcp-test-client.builder.d.ts +1 -1
- package/client/mcp-test-client.d.ts.map +1 -1
- package/client/mcp-test-client.types.d.ts +3 -3
- package/client/mcp-test-client.types.d.ts.map +1 -1
- package/errors/index.d.ts.map +1 -1
- package/esm/fixtures/index.mjs +255 -36
- package/esm/index.mjs +291 -122
- package/esm/matchers/index.mjs +8 -32
- package/esm/package.json +6 -6
- package/esm/perf/index.mjs +239 -36
- package/esm/setup.mjs +8 -32
- package/example-tools/index.d.ts +1 -1
- package/example-tools/index.d.ts.map +1 -1
- package/example-tools/tool-configs.d.ts +4 -10
- package/example-tools/tool-configs.d.ts.map +1 -1
- package/fixtures/index.js +241 -36
- package/fixtures/test-fixture.d.ts.map +1 -1
- package/index.d.ts +2 -1
- package/index.d.ts.map +1 -1
- package/index.js +283 -126
- package/matchers/index.js +8 -32
- package/package.json +6 -6
- package/perf/index.js +232 -36
- package/platform/platform-client-info.d.ts +2 -1
- package/platform/platform-client-info.d.ts.map +1 -1
- package/platform/platform-types.d.ts +11 -16
- package/platform/platform-types.d.ts.map +1 -1
- package/playwright/index.d.ts +1 -1
- package/raw-client/index.d.ts +7 -0
- package/raw-client/index.d.ts.map +1 -0
- package/server/index.d.ts +1 -1
- package/server/index.d.ts.map +1 -1
- package/server/port-registry.d.ts +24 -6
- package/server/port-registry.d.ts.map +1 -1
- package/server/test-server.d.ts +1 -1
- package/server/test-server.d.ts.map +1 -1
- package/setup.js +8 -32
- package/ui/ui-assertions.d.ts +3 -4
- package/ui/ui-assertions.d.ts.map +1 -1
- package/ui/ui-matchers.d.ts +1 -2
- 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
|
|
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
|
|
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
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
|
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-
|
|
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
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
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
|
-
|
|
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
|
|
4054
|
-
|
|
4055
|
-
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4059
|
-
|
|
4060
|
-
|
|
4061
|
-
|
|
4062
|
-
|
|
4063
|
-
|
|
4064
|
-
|
|
4065
|
-
|
|
4066
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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 = {
|
|
@@ -4504,6 +4707,20 @@ function hasMimeType(result, mimeType) {
|
|
|
4504
4707
|
}
|
|
4505
4708
|
|
|
4506
4709
|
// libs/testing/src/fixtures/test-fixture.ts
|
|
4710
|
+
import {
|
|
4711
|
+
describe as _describe,
|
|
4712
|
+
beforeAll as _beforeAll,
|
|
4713
|
+
beforeEach as _beforeEach,
|
|
4714
|
+
afterEach as _afterEach,
|
|
4715
|
+
afterAll as _afterAll,
|
|
4716
|
+
it as _it
|
|
4717
|
+
} from "@jest/globals";
|
|
4718
|
+
var describe2 = _describe;
|
|
4719
|
+
var beforeAll2 = _beforeAll;
|
|
4720
|
+
var beforeEach2 = _beforeEach;
|
|
4721
|
+
var afterEach2 = _afterEach;
|
|
4722
|
+
var afterAll2 = _afterAll;
|
|
4723
|
+
var it2 = _it;
|
|
4507
4724
|
var currentConfig = {};
|
|
4508
4725
|
var serverInstance = null;
|
|
4509
4726
|
var tokenFactory = null;
|
|
@@ -4675,7 +4892,7 @@ function resolveServerCommand(server) {
|
|
|
4675
4892
|
return `npx tsx ${server}`;
|
|
4676
4893
|
}
|
|
4677
4894
|
function testWithFixtures(name, fn) {
|
|
4678
|
-
|
|
4895
|
+
it2(name, async () => {
|
|
4679
4896
|
const fixtures = await createTestFixtures();
|
|
4680
4897
|
let testFailed = false;
|
|
4681
4898
|
try {
|
|
@@ -4690,12 +4907,12 @@ function testWithFixtures(name, fn) {
|
|
|
4690
4907
|
}
|
|
4691
4908
|
function use(config) {
|
|
4692
4909
|
currentConfig = { ...currentConfig, ...config };
|
|
4693
|
-
|
|
4910
|
+
afterAll2(async () => {
|
|
4694
4911
|
await cleanupSharedResources();
|
|
4695
4912
|
});
|
|
4696
4913
|
}
|
|
4697
4914
|
function skip(name, fn) {
|
|
4698
|
-
|
|
4915
|
+
it2.skip(name, async () => {
|
|
4699
4916
|
const fixtures = await createTestFixtures();
|
|
4700
4917
|
let testFailed = false;
|
|
4701
4918
|
try {
|
|
@@ -4709,7 +4926,7 @@ function skip(name, fn) {
|
|
|
4709
4926
|
});
|
|
4710
4927
|
}
|
|
4711
4928
|
function only(name, fn) {
|
|
4712
|
-
|
|
4929
|
+
it2.only(name, async () => {
|
|
4713
4930
|
const fixtures = await createTestFixtures();
|
|
4714
4931
|
let testFailed = false;
|
|
4715
4932
|
try {
|
|
@@ -4723,15 +4940,15 @@ function only(name, fn) {
|
|
|
4723
4940
|
});
|
|
4724
4941
|
}
|
|
4725
4942
|
function todo(name) {
|
|
4726
|
-
|
|
4943
|
+
it2.todo(name);
|
|
4727
4944
|
}
|
|
4728
4945
|
var test = testWithFixtures;
|
|
4729
4946
|
test.use = use;
|
|
4730
|
-
test.describe =
|
|
4731
|
-
test.beforeAll =
|
|
4732
|
-
test.beforeEach =
|
|
4733
|
-
test.afterEach =
|
|
4734
|
-
test.afterAll =
|
|
4947
|
+
test.describe = describe2;
|
|
4948
|
+
test.beforeAll = beforeAll2;
|
|
4949
|
+
test.beforeEach = beforeEach2;
|
|
4950
|
+
test.afterEach = afterEach2;
|
|
4951
|
+
test.afterAll = afterAll2;
|
|
4735
4952
|
test.skip = skip;
|
|
4736
4953
|
test.only = only;
|
|
4737
4954
|
test.todo = todo;
|
|
@@ -4742,15 +4959,10 @@ var expect = jestExpect;
|
|
|
4742
4959
|
|
|
4743
4960
|
// libs/testing/src/platform/platform-types.ts
|
|
4744
4961
|
function getPlatformMetaNamespace(platform) {
|
|
4745
|
-
|
|
4746
|
-
case "openai":
|
|
4747
|
-
return "openai";
|
|
4748
|
-
default:
|
|
4749
|
-
return "ui";
|
|
4750
|
-
}
|
|
4962
|
+
return "ui";
|
|
4751
4963
|
}
|
|
4752
4964
|
function getPlatformMimeType(platform) {
|
|
4753
|
-
return
|
|
4965
|
+
return "text/html;profile=mcp-app";
|
|
4754
4966
|
}
|
|
4755
4967
|
function isOpenAIPlatform(platform) {
|
|
4756
4968
|
return platform === "openai";
|
|
@@ -4759,31 +4971,16 @@ function isExtAppsPlatform(platform) {
|
|
|
4759
4971
|
return platform === "ext-apps";
|
|
4760
4972
|
}
|
|
4761
4973
|
function isUiPlatform(platform) {
|
|
4762
|
-
return
|
|
4974
|
+
return true;
|
|
4763
4975
|
}
|
|
4764
4976
|
function getToolsListMetaPrefixes(platform) {
|
|
4765
|
-
|
|
4766
|
-
case "openai":
|
|
4767
|
-
return ["openai/"];
|
|
4768
|
-
default:
|
|
4769
|
-
return ["ui/"];
|
|
4770
|
-
}
|
|
4977
|
+
return ["ui/"];
|
|
4771
4978
|
}
|
|
4772
4979
|
function getToolCallMetaPrefixes(platform) {
|
|
4773
|
-
|
|
4774
|
-
case "openai":
|
|
4775
|
-
return ["openai/"];
|
|
4776
|
-
default:
|
|
4777
|
-
return ["ui/"];
|
|
4778
|
-
}
|
|
4980
|
+
return ["ui/"];
|
|
4779
4981
|
}
|
|
4780
4982
|
function getForbiddenMetaPrefixes(platform) {
|
|
4781
|
-
|
|
4782
|
-
case "openai":
|
|
4783
|
-
return ["ui/", "frontmcp/"];
|
|
4784
|
-
default:
|
|
4785
|
-
return ["openai/", "frontmcp/"];
|
|
4786
|
-
}
|
|
4983
|
+
return ["openai/", "frontmcp/"];
|
|
4787
4984
|
}
|
|
4788
4985
|
|
|
4789
4986
|
// libs/testing/src/ui/ui-matchers.ts
|
|
@@ -4890,12 +5087,12 @@ var toHaveWidgetMetadata = function(received) {
|
|
|
4890
5087
|
};
|
|
4891
5088
|
}
|
|
4892
5089
|
const hasUiHtml = Boolean(meta["ui/html"]);
|
|
4893
|
-
const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
|
|
4894
5090
|
const hasMimeType2 = Boolean(meta["ui/mimeType"]);
|
|
4895
|
-
const
|
|
5091
|
+
const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
|
|
5092
|
+
const pass = hasUiHtml || hasMimeType2 || hasUiObject;
|
|
4896
5093
|
return {
|
|
4897
5094
|
pass,
|
|
4898
|
-
message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html,
|
|
5095
|
+
message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)"
|
|
4899
5096
|
};
|
|
4900
5097
|
};
|
|
4901
5098
|
var toHaveCssClass = function(received, className) {
|
|
@@ -5058,14 +5255,7 @@ var toHavePlatformMimeType = function(received, platform) {
|
|
|
5058
5255
|
message: () => `Expected _meta to have MIME type "${expectedMimeType}" for platform "${platform}", but no _meta found`
|
|
5059
5256
|
};
|
|
5060
5257
|
}
|
|
5061
|
-
|
|
5062
|
-
switch (platform) {
|
|
5063
|
-
case "openai":
|
|
5064
|
-
mimeTypeKey = "openai/mimeType";
|
|
5065
|
-
break;
|
|
5066
|
-
default:
|
|
5067
|
-
mimeTypeKey = "ui/mimeType";
|
|
5068
|
-
}
|
|
5258
|
+
const mimeTypeKey = "ui/mimeType";
|
|
5069
5259
|
const actualMimeType = meta[mimeTypeKey];
|
|
5070
5260
|
const pass = actualMimeType === expectedMimeType;
|
|
5071
5261
|
return {
|
|
@@ -5081,14 +5271,7 @@ var toHavePlatformHtml = function(received, platform) {
|
|
|
5081
5271
|
message: () => `Expected _meta to have platform HTML for "${platform}", but no _meta found`
|
|
5082
5272
|
};
|
|
5083
5273
|
}
|
|
5084
|
-
|
|
5085
|
-
switch (platform) {
|
|
5086
|
-
case "openai":
|
|
5087
|
-
htmlKey = "openai/html";
|
|
5088
|
-
break;
|
|
5089
|
-
default:
|
|
5090
|
-
htmlKey = "ui/html";
|
|
5091
|
-
}
|
|
5274
|
+
const htmlKey = "ui/html";
|
|
5092
5275
|
const html = meta[htmlKey];
|
|
5093
5276
|
const pass = typeof html === "string" && html.length > 0;
|
|
5094
5277
|
return {
|
|
@@ -5952,7 +6135,7 @@ var UIAssertions = {
|
|
|
5952
6135
|
},
|
|
5953
6136
|
/**
|
|
5954
6137
|
* Assert that widget metadata is present in the result.
|
|
5955
|
-
* Checks for ui/html,
|
|
6138
|
+
* Checks for ui/html, ui/mimeType, or nested ui object with resourceUri.
|
|
5956
6139
|
* @param result - The tool result wrapper
|
|
5957
6140
|
* @throws Error if widget metadata is missing
|
|
5958
6141
|
*/
|
|
@@ -5962,10 +6145,10 @@ var UIAssertions = {
|
|
|
5962
6145
|
throw new Error("Expected tool result to have _meta with widget metadata");
|
|
5963
6146
|
}
|
|
5964
6147
|
const hasUiHtml = Boolean(meta["ui/html"]);
|
|
5965
|
-
const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
|
|
5966
6148
|
const hasMimeType2 = Boolean(meta["ui/mimeType"]);
|
|
5967
|
-
|
|
5968
|
-
|
|
6149
|
+
const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
|
|
6150
|
+
if (!hasUiHtml && !hasMimeType2 && !hasUiObject) {
|
|
6151
|
+
throw new Error("Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)");
|
|
5969
6152
|
}
|
|
5970
6153
|
},
|
|
5971
6154
|
/**
|
|
@@ -5993,7 +6176,7 @@ var UIAssertions = {
|
|
|
5993
6176
|
// ═══════════════════════════════════════════════════════════════════
|
|
5994
6177
|
/**
|
|
5995
6178
|
* Assert tool result has correct meta keys for OpenAI platform.
|
|
5996
|
-
* Verifies
|
|
6179
|
+
* Verifies ui/* keys are present and openai/*, frontmcp/* keys are absent.
|
|
5997
6180
|
* @param result - The tool result wrapper
|
|
5998
6181
|
* @throws Error if meta keys don't match OpenAI expectations
|
|
5999
6182
|
*/
|
|
@@ -6076,14 +6259,7 @@ var UIAssertions = {
|
|
|
6076
6259
|
if (!meta) {
|
|
6077
6260
|
throw new Error(`Expected tool result to have _meta with MIME type for platform "${platform}"`);
|
|
6078
6261
|
}
|
|
6079
|
-
|
|
6080
|
-
switch (platform) {
|
|
6081
|
-
case "openai":
|
|
6082
|
-
mimeTypeKey = "openai/mimeType";
|
|
6083
|
-
break;
|
|
6084
|
-
default:
|
|
6085
|
-
mimeTypeKey = "ui/mimeType";
|
|
6086
|
-
}
|
|
6262
|
+
const mimeTypeKey = "ui/mimeType";
|
|
6087
6263
|
const actualMimeType = meta[mimeTypeKey];
|
|
6088
6264
|
if (actualMimeType !== expectedMimeType) {
|
|
6089
6265
|
throw new Error(
|
|
@@ -6103,14 +6279,7 @@ var UIAssertions = {
|
|
|
6103
6279
|
if (!meta) {
|
|
6104
6280
|
throw new Error(`Expected tool result to have _meta with platform HTML for "${platform}"`);
|
|
6105
6281
|
}
|
|
6106
|
-
|
|
6107
|
-
switch (platform) {
|
|
6108
|
-
case "openai":
|
|
6109
|
-
htmlKey = "openai/html";
|
|
6110
|
-
break;
|
|
6111
|
-
default:
|
|
6112
|
-
htmlKey = "ui/html";
|
|
6113
|
-
}
|
|
6282
|
+
const htmlKey = "ui/html";
|
|
6114
6283
|
const html = meta[htmlKey];
|
|
6115
6284
|
if (typeof html !== "string" || html.length === 0) {
|
|
6116
6285
|
throw new Error(
|
|
@@ -6250,18 +6419,12 @@ function generateFullUIToolOutput(input) {
|
|
|
6250
6419
|
timestamp: Date.now()
|
|
6251
6420
|
};
|
|
6252
6421
|
}
|
|
6253
|
-
var EXPECTED_OPENAI_TOOLS_LIST_META_KEYS = [
|
|
6254
|
-
|
|
6255
|
-
"openai/resultCanProduceWidget",
|
|
6256
|
-
"openai/widgetAccessible"
|
|
6257
|
-
];
|
|
6258
|
-
var EXPECTED_OPENAI_TOOL_CALL_META_KEYS = ["openai/html", "openai/mimeType", "openai/type"];
|
|
6422
|
+
var EXPECTED_OPENAI_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
|
|
6423
|
+
var EXPECTED_OPENAI_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
|
|
6259
6424
|
var EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
|
|
6260
6425
|
var EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS = ["ui/html", "ui/mimeType", "ui/type"];
|
|
6261
6426
|
var EXPECTED_GENERIC_TOOLS_LIST_META_KEYS = ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
|
|
6262
6427
|
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
6428
|
|
|
6266
6429
|
// libs/testing/src/perf/metrics-collector.ts
|
|
6267
6430
|
function isGcAvailable() {
|
|
@@ -7806,6 +7969,10 @@ async function saveReports(measurements, outputDir, baseline, gitInfo) {
|
|
|
7806
7969
|
function createReportGenerator() {
|
|
7807
7970
|
return new ReportGenerator();
|
|
7808
7971
|
}
|
|
7972
|
+
|
|
7973
|
+
// libs/testing/src/raw-client/index.ts
|
|
7974
|
+
import { Client } from "@frontmcp/protocol";
|
|
7975
|
+
import { StdioClientTransport } from "@frontmcp/protocol";
|
|
7809
7976
|
export {
|
|
7810
7977
|
AssertionError,
|
|
7811
7978
|
AuthHeaders,
|
|
@@ -7816,14 +7983,16 @@ export {
|
|
|
7816
7983
|
DefaultMockRegistry,
|
|
7817
7984
|
EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS,
|
|
7818
7985
|
EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS,
|
|
7819
|
-
|
|
7820
|
-
|
|
7986
|
+
EXPECTED_GENERIC_TOOLS_LIST_META_KEYS,
|
|
7987
|
+
EXPECTED_GENERIC_TOOL_CALL_META_KEYS,
|
|
7821
7988
|
EXPECTED_OPENAI_TOOLS_LIST_META_KEYS,
|
|
7822
7989
|
EXPECTED_OPENAI_TOOL_CALL_META_KEYS,
|
|
7823
7990
|
FULL_UI_TOOL_CONFIG,
|
|
7824
7991
|
LeakDetector,
|
|
7825
7992
|
McpAssertions,
|
|
7993
|
+
Client as McpClient,
|
|
7826
7994
|
McpProtocolError,
|
|
7995
|
+
StdioClientTransport as McpStdioClientTransport,
|
|
7827
7996
|
McpTestClient,
|
|
7828
7997
|
McpTestClientBuilder,
|
|
7829
7998
|
MetricsCollector,
|