@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/matchers/index.mjs
CHANGED
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
// libs/testing/src/platform/platform-types.ts
|
|
2
2
|
function getPlatformMimeType(platform) {
|
|
3
|
-
return
|
|
3
|
+
return "text/html;profile=mcp-app";
|
|
4
4
|
}
|
|
5
5
|
function getToolCallMetaPrefixes(platform) {
|
|
6
|
-
|
|
7
|
-
case "openai":
|
|
8
|
-
return ["openai/"];
|
|
9
|
-
default:
|
|
10
|
-
return ["ui/"];
|
|
11
|
-
}
|
|
6
|
+
return ["ui/"];
|
|
12
7
|
}
|
|
13
8
|
function getForbiddenMetaPrefixes(platform) {
|
|
14
|
-
|
|
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
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
package/esm/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@frontmcp/testing",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0-beta.10",
|
|
4
4
|
"description": "E2E testing framework for FrontMCP servers - MCP client, auth mocks, Playwright integration",
|
|
5
5
|
"author": "AgentFront <info@agentfront.dev>",
|
|
6
6
|
"homepage": "https://docs.agentfront.dev",
|
|
@@ -87,8 +87,8 @@
|
|
|
87
87
|
"./esm": null
|
|
88
88
|
},
|
|
89
89
|
"peerDependencies": {
|
|
90
|
-
"@frontmcp/sdk": "0.
|
|
91
|
-
"@frontmcp/ui": "0.
|
|
90
|
+
"@frontmcp/sdk": "1.0.0-beta.10",
|
|
91
|
+
"@frontmcp/ui": "1.0.0-beta.10",
|
|
92
92
|
"@playwright/test": "^1.40.0",
|
|
93
93
|
"jest": "^29.0.0",
|
|
94
94
|
"@jest/globals": "^29.0.0"
|
|
@@ -108,11 +108,11 @@
|
|
|
108
108
|
}
|
|
109
109
|
},
|
|
110
110
|
"engines": {
|
|
111
|
-
"node": ">=
|
|
111
|
+
"node": ">=24.0.0"
|
|
112
112
|
},
|
|
113
113
|
"dependencies": {
|
|
114
|
-
"@frontmcp/utils": "0.
|
|
115
|
-
"@
|
|
114
|
+
"@frontmcp/utils": "1.0.0-beta.10",
|
|
115
|
+
"@frontmcp/protocol": "1.0.0-beta.10",
|
|
116
116
|
"jose": "^6.0.11",
|
|
117
117
|
"tslib": "^2.3.0"
|
|
118
118
|
},
|
package/esm/perf/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/perf/metrics-collector.ts
|
|
2
9
|
function isGcAvailable() {
|
|
3
10
|
return typeof global.gc === "function";
|
|
@@ -829,7 +836,7 @@ function getPlatformCapabilities(platform) {
|
|
|
829
836
|
...baseCapabilities,
|
|
830
837
|
experimental: {
|
|
831
838
|
[MCP_APPS_EXTENSION_KEY]: {
|
|
832
|
-
mimeTypes: ["text/html
|
|
839
|
+
mimeTypes: ["text/html;profile=mcp-app"]
|
|
833
840
|
}
|
|
834
841
|
}
|
|
835
842
|
};
|
|
@@ -950,7 +957,7 @@ var McpTestClientBuilder = class {
|
|
|
950
957
|
* .withCapabilities({
|
|
951
958
|
* sampling: {},
|
|
952
959
|
* experimental: {
|
|
953
|
-
* 'io.modelcontextprotocol/ui': { mimeTypes: ['text/html
|
|
960
|
+
* 'io.modelcontextprotocol/ui': { mimeTypes: ['text/html;profile=mcp-app'] }
|
|
954
961
|
* }
|
|
955
962
|
* })
|
|
956
963
|
* .buildAndConnect();
|
|
@@ -1932,11 +1939,24 @@ var McpTestClient = class {
|
|
|
1932
1939
|
this.log("debug", `Connecting to ${this.config.baseUrl}...`);
|
|
1933
1940
|
this.transport = this.createTransport();
|
|
1934
1941
|
await this.transport.connect();
|
|
1935
|
-
const
|
|
1936
|
-
|
|
1937
|
-
|
|
1942
|
+
const maxRetries = 3;
|
|
1943
|
+
const retryDelayMs = 500;
|
|
1944
|
+
let lastError;
|
|
1945
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
1946
|
+
const initResponse = await this.initialize();
|
|
1947
|
+
if (initResponse.success && initResponse.data) {
|
|
1948
|
+
this.initResult = initResponse.data;
|
|
1949
|
+
break;
|
|
1950
|
+
}
|
|
1951
|
+
lastError = initResponse.error?.message ?? "Unknown error";
|
|
1952
|
+
if (attempt < maxRetries) {
|
|
1953
|
+
this.log("debug", `MCP init attempt ${attempt} failed (${lastError}), retrying in ${retryDelayMs}ms...`);
|
|
1954
|
+
await new Promise((resolve) => setTimeout(resolve, retryDelayMs));
|
|
1955
|
+
}
|
|
1956
|
+
}
|
|
1957
|
+
if (!this.initResult) {
|
|
1958
|
+
throw new Error(`Failed to initialize MCP connection after ${maxRetries} attempts: ${lastError}`);
|
|
1938
1959
|
}
|
|
1939
|
-
this.initResult = initResponse.data;
|
|
1940
1960
|
this._sessionId = this.transport.getSessionId();
|
|
1941
1961
|
this._sessionInfo = {
|
|
1942
1962
|
id: this._sessionId ?? `session-${Date.now()}`,
|
|
@@ -2627,7 +2647,7 @@ var McpTestClient = class {
|
|
|
2627
2647
|
const raw = response.data ?? { content: [] };
|
|
2628
2648
|
const isError = !response.success || raw.isError === true;
|
|
2629
2649
|
const meta = raw._meta;
|
|
2630
|
-
const hasUI = meta?.["ui/html"] !== void 0 || meta?.["ui/component"] !== void 0
|
|
2650
|
+
const hasUI = meta?.["ui/html"] !== void 0 || meta?.["ui/component"] !== void 0;
|
|
2631
2651
|
const structuredContent = raw["structuredContent"];
|
|
2632
2652
|
return {
|
|
2633
2653
|
raw,
|
|
@@ -2979,6 +2999,7 @@ var TestTokenFactory = class {
|
|
|
2979
2999
|
|
|
2980
3000
|
// libs/testing/src/server/test-server.ts
|
|
2981
3001
|
import { spawn } from "child_process";
|
|
3002
|
+
import { sha256Hex } from "@frontmcp/utils";
|
|
2982
3003
|
|
|
2983
3004
|
// libs/testing/src/errors/index.ts
|
|
2984
3005
|
var TestClientError = class extends Error {
|
|
@@ -3026,9 +3047,16 @@ var E2E_PORT_RANGES = {
|
|
|
3026
3047
|
"demo-e2e-agents": { start: 50270, size: 10 },
|
|
3027
3048
|
"demo-e2e-transport-recreation": { start: 50280, size: 10 },
|
|
3028
3049
|
"demo-e2e-jobs": { start: 50290, size: 10 },
|
|
3029
|
-
// Infrastructure E2E tests (50300-
|
|
3050
|
+
// Infrastructure E2E tests (50300-50409)
|
|
3030
3051
|
"demo-e2e-redis": { start: 50300, size: 10 },
|
|
3031
3052
|
"demo-e2e-serverless": { start: 50310, size: 10 },
|
|
3053
|
+
"demo-e2e-uipack": { start: 50320, size: 10 },
|
|
3054
|
+
"demo-e2e-agent-adapters": { start: 50330, size: 10 },
|
|
3055
|
+
"demo-e2e-guard": { start: 50340, size: 10 },
|
|
3056
|
+
// ESM E2E tests (50400-50449)
|
|
3057
|
+
"esm-package-server": { start: 50400, size: 10 },
|
|
3058
|
+
"esm-package-server-hot-reload": { start: 50410, size: 10 },
|
|
3059
|
+
"esm-package-server-cli": { start: 50420, size: 10 },
|
|
3032
3060
|
// Mock servers and utilities (50900-50999)
|
|
3033
3061
|
"mock-oauth": { start: 50900, size: 10 },
|
|
3034
3062
|
"mock-api": { start: 50910, size: 10 },
|
|
@@ -3100,7 +3128,7 @@ async function tryReservePort(port, project) {
|
|
|
3100
3128
|
server.once("error", () => {
|
|
3101
3129
|
resolve(false);
|
|
3102
3130
|
});
|
|
3103
|
-
server.listen(port,
|
|
3131
|
+
server.listen(port, () => {
|
|
3104
3132
|
reservedPorts.set(port, {
|
|
3105
3133
|
port,
|
|
3106
3134
|
project,
|
|
@@ -3141,7 +3169,7 @@ async function isPortAvailable(port) {
|
|
|
3141
3169
|
server.once("error", () => {
|
|
3142
3170
|
resolve(false);
|
|
3143
3171
|
});
|
|
3144
|
-
server.listen(port,
|
|
3172
|
+
server.listen(port, () => {
|
|
3145
3173
|
server.close(() => {
|
|
3146
3174
|
resolve(true);
|
|
3147
3175
|
});
|
|
@@ -3179,15 +3207,37 @@ var TestServer = class _TestServer {
|
|
|
3179
3207
|
*/
|
|
3180
3208
|
static async start(options) {
|
|
3181
3209
|
const project = options.project ?? "default";
|
|
3182
|
-
const
|
|
3183
|
-
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3210
|
+
const maxAttempts = 3;
|
|
3211
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3212
|
+
const { port, release } = await reservePort(project, options.port);
|
|
3213
|
+
const server = new _TestServer(options, port, release);
|
|
3214
|
+
try {
|
|
3215
|
+
await server.startProcess();
|
|
3216
|
+
return server;
|
|
3217
|
+
} catch (error) {
|
|
3218
|
+
try {
|
|
3219
|
+
await server.stop();
|
|
3220
|
+
} catch (cleanupError) {
|
|
3221
|
+
if (options.debug || DEBUG_SERVER) {
|
|
3222
|
+
const msg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
3223
|
+
console.warn(`[TestServer] Cleanup failed after startup error: ${msg}`);
|
|
3224
|
+
}
|
|
3225
|
+
}
|
|
3226
|
+
const isEADDRINUSE = error instanceof Error && (error.message.includes("EADDRINUSE") || server.getLogs().some((l) => l.includes("EADDRINUSE")));
|
|
3227
|
+
if (isEADDRINUSE && attempt < maxAttempts) {
|
|
3228
|
+
const delayMs = attempt * 500;
|
|
3229
|
+
if (options.debug || DEBUG_SERVER) {
|
|
3230
|
+
console.warn(
|
|
3231
|
+
`[TestServer] EADDRINUSE on port ${port}, retrying in ${delayMs}ms (attempt ${attempt}/${maxAttempts})`
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3234
|
+
await sleep4(delayMs);
|
|
3235
|
+
continue;
|
|
3236
|
+
}
|
|
3237
|
+
throw error;
|
|
3238
|
+
}
|
|
3189
3239
|
}
|
|
3190
|
-
|
|
3240
|
+
throw new Error(`[TestServer] Failed to start after ${maxAttempts} attempts`);
|
|
3191
3241
|
}
|
|
3192
3242
|
/**
|
|
3193
3243
|
* Start an Nx project as test server
|
|
@@ -3198,22 +3248,44 @@ var TestServer = class _TestServer {
|
|
|
3198
3248
|
`Invalid project name: ${project}. Must contain only alphanumeric, underscore, and hyphen characters.`
|
|
3199
3249
|
);
|
|
3200
3250
|
}
|
|
3201
|
-
const
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
|
|
3209
|
-
|
|
3210
|
-
|
|
3211
|
-
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3251
|
+
const maxAttempts = 3;
|
|
3252
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
3253
|
+
const { port, release } = await reservePort(project, options.port);
|
|
3254
|
+
const serverOptions = {
|
|
3255
|
+
...options,
|
|
3256
|
+
port,
|
|
3257
|
+
project,
|
|
3258
|
+
command: `npx nx serve ${project} --port ${port}`,
|
|
3259
|
+
cwd: options.cwd ?? process.cwd()
|
|
3260
|
+
};
|
|
3261
|
+
const server = new _TestServer(serverOptions, port, release);
|
|
3262
|
+
try {
|
|
3263
|
+
await server.startProcess();
|
|
3264
|
+
return server;
|
|
3265
|
+
} catch (error) {
|
|
3266
|
+
try {
|
|
3267
|
+
await server.stop();
|
|
3268
|
+
} catch (cleanupError) {
|
|
3269
|
+
if (options.debug || DEBUG_SERVER) {
|
|
3270
|
+
const msg = cleanupError instanceof Error ? cleanupError.message : String(cleanupError);
|
|
3271
|
+
console.warn(`[TestServer] Cleanup failed after startup error: ${msg}`);
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
const isEADDRINUSE = error instanceof Error && (error.message.includes("EADDRINUSE") || server.getLogs().some((l) => l.includes("EADDRINUSE")));
|
|
3275
|
+
if (isEADDRINUSE && attempt < maxAttempts) {
|
|
3276
|
+
const delayMs = attempt * 500;
|
|
3277
|
+
if (options.debug || DEBUG_SERVER) {
|
|
3278
|
+
console.warn(
|
|
3279
|
+
`[TestServer] EADDRINUSE on port ${port}, retrying in ${delayMs}ms (attempt ${attempt}/${maxAttempts})`
|
|
3280
|
+
);
|
|
3281
|
+
}
|
|
3282
|
+
await sleep4(delayMs);
|
|
3283
|
+
continue;
|
|
3284
|
+
}
|
|
3285
|
+
throw error;
|
|
3286
|
+
}
|
|
3215
3287
|
}
|
|
3216
|
-
|
|
3288
|
+
throw new Error(`[TestServer] Failed to start after ${maxAttempts} attempts`);
|
|
3217
3289
|
}
|
|
3218
3290
|
/**
|
|
3219
3291
|
* Create a test server connected to an already running server
|
|
@@ -3250,7 +3322,21 @@ var TestServer = class _TestServer {
|
|
|
3250
3322
|
}
|
|
3251
3323
|
if (this.process) {
|
|
3252
3324
|
this.log("Stopping server...");
|
|
3253
|
-
this.process.
|
|
3325
|
+
if (this.process.exitCode !== null || this.process.signalCode !== null) {
|
|
3326
|
+
this.log(`Server already exited (code: ${this.process.exitCode}, signal: ${this.process.signalCode})`);
|
|
3327
|
+
this.process = null;
|
|
3328
|
+
return;
|
|
3329
|
+
}
|
|
3330
|
+
const pid = this.process.pid;
|
|
3331
|
+
try {
|
|
3332
|
+
if (pid !== void 0) {
|
|
3333
|
+
process.kill(-pid, "SIGTERM");
|
|
3334
|
+
} else {
|
|
3335
|
+
this.process.kill("SIGTERM");
|
|
3336
|
+
}
|
|
3337
|
+
} catch {
|
|
3338
|
+
this.process.kill("SIGTERM");
|
|
3339
|
+
}
|
|
3254
3340
|
const exitPromise = new Promise((resolve) => {
|
|
3255
3341
|
if (this.process) {
|
|
3256
3342
|
this.process.once("exit", () => resolve());
|
|
@@ -3261,7 +3347,16 @@ var TestServer = class _TestServer {
|
|
|
3261
3347
|
const killTimeout = setTimeout(() => {
|
|
3262
3348
|
if (this.process) {
|
|
3263
3349
|
this.log("Force killing server after timeout...");
|
|
3264
|
-
this.process.
|
|
3350
|
+
const killPid = this.process.pid;
|
|
3351
|
+
try {
|
|
3352
|
+
if (killPid !== void 0) {
|
|
3353
|
+
process.kill(-killPid, "SIGKILL");
|
|
3354
|
+
} else {
|
|
3355
|
+
this.process.kill("SIGKILL");
|
|
3356
|
+
}
|
|
3357
|
+
} catch {
|
|
3358
|
+
this.process.kill("SIGKILL");
|
|
3359
|
+
}
|
|
3265
3360
|
}
|
|
3266
3361
|
}, 5e3);
|
|
3267
3362
|
await exitPromise;
|
|
@@ -3326,14 +3421,17 @@ var TestServer = class _TestServer {
|
|
|
3326
3421
|
...this.options.env,
|
|
3327
3422
|
PORT: String(this.options.port)
|
|
3328
3423
|
};
|
|
3424
|
+
const runtimeEnv = withWorkspaceProtocolFallback(env, this.options.cwd);
|
|
3329
3425
|
if (this.portRelease) {
|
|
3330
3426
|
await this.portRelease();
|
|
3331
3427
|
this.portRelease = null;
|
|
3428
|
+
await sleep4(300);
|
|
3332
3429
|
}
|
|
3333
3430
|
this.process = spawn(this.options.command, [], {
|
|
3334
3431
|
cwd: this.options.cwd,
|
|
3335
|
-
env,
|
|
3432
|
+
env: runtimeEnv,
|
|
3336
3433
|
shell: true,
|
|
3434
|
+
detached: true,
|
|
3337
3435
|
stdio: ["pipe", "pipe", "pipe"]
|
|
3338
3436
|
});
|
|
3339
3437
|
if (this.process.pid !== void 0) {
|
|
@@ -3485,6 +3583,111 @@ TIP: Set DEBUG_SERVER=1 or DEBUG=1 environment variable for verbose output`
|
|
|
3485
3583
|
function sleep4(ms) {
|
|
3486
3584
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
3487
3585
|
}
|
|
3586
|
+
function withWorkspaceProtocolFallback(env, cwd) {
|
|
3587
|
+
if (findInstalledProtocolPackageDir(cwd)) {
|
|
3588
|
+
return env;
|
|
3589
|
+
}
|
|
3590
|
+
try {
|
|
3591
|
+
const workspacePackageDir = findWorkspaceProtocolDir(cwd);
|
|
3592
|
+
if (!workspacePackageDir) {
|
|
3593
|
+
return env;
|
|
3594
|
+
}
|
|
3595
|
+
ensureWorkspaceProtocolLink(cwd, workspacePackageDir);
|
|
3596
|
+
if (findInstalledProtocolPackageDir(cwd)) {
|
|
3597
|
+
return env;
|
|
3598
|
+
}
|
|
3599
|
+
return withProtocolNodePathAlias(env, cwd, workspacePackageDir);
|
|
3600
|
+
} catch (err) {
|
|
3601
|
+
if (DEBUG_SERVER) {
|
|
3602
|
+
console.error(
|
|
3603
|
+
`[TestServer] Workspace protocol fallback failed: ${err instanceof Error ? err.message : String(err)}`
|
|
3604
|
+
);
|
|
3605
|
+
}
|
|
3606
|
+
return env;
|
|
3607
|
+
}
|
|
3608
|
+
}
|
|
3609
|
+
function ensureWorkspaceProtocolLink(cwd, workspacePackageDir) {
|
|
3610
|
+
const fs = __require("node:fs");
|
|
3611
|
+
const path = __require("node:path");
|
|
3612
|
+
const nodeModulesDir = findWorkspaceNodeModulesDir(cwd);
|
|
3613
|
+
if (!nodeModulesDir) {
|
|
3614
|
+
return;
|
|
3615
|
+
}
|
|
3616
|
+
const scopeDir = path.join(nodeModulesDir, "@frontmcp");
|
|
3617
|
+
const aliasPackageDir = path.join(scopeDir, "protocol");
|
|
3618
|
+
if (fs.existsSync(aliasPackageDir)) {
|
|
3619
|
+
return;
|
|
3620
|
+
}
|
|
3621
|
+
fs.mkdirSync(scopeDir, { recursive: true });
|
|
3622
|
+
try {
|
|
3623
|
+
fs.symlinkSync(workspacePackageDir, aliasPackageDir, process.platform === "win32" ? "junction" : "dir");
|
|
3624
|
+
} catch (error) {
|
|
3625
|
+
const err = error;
|
|
3626
|
+
if (err.code !== "EEXIST") {
|
|
3627
|
+
throw error;
|
|
3628
|
+
}
|
|
3629
|
+
}
|
|
3630
|
+
}
|
|
3631
|
+
function withProtocolNodePathAlias(env, cwd, workspacePackageDir) {
|
|
3632
|
+
const fs = __require("node:fs");
|
|
3633
|
+
const os = __require("node:os");
|
|
3634
|
+
const path = __require("node:path");
|
|
3635
|
+
const aliasRoot = path.join(os.tmpdir(), "frontmcp-test-node-path", sha256Hex(cwd).slice(0, 12));
|
|
3636
|
+
const scopeDir = path.join(aliasRoot, "@frontmcp");
|
|
3637
|
+
const aliasPackageDir = path.join(scopeDir, "protocol");
|
|
3638
|
+
fs.mkdirSync(scopeDir, { recursive: true });
|
|
3639
|
+
try {
|
|
3640
|
+
if (!fs.existsSync(aliasPackageDir)) {
|
|
3641
|
+
fs.symlinkSync(workspacePackageDir, aliasPackageDir, process.platform === "win32" ? "junction" : "dir");
|
|
3642
|
+
}
|
|
3643
|
+
} catch (error) {
|
|
3644
|
+
const err = error;
|
|
3645
|
+
if (err.code !== "EEXIST") throw error;
|
|
3646
|
+
}
|
|
3647
|
+
const existingNodePath = env["NODE_PATH"];
|
|
3648
|
+
const nodePathEntries = [aliasRoot, ...existingNodePath ? existingNodePath.split(path.delimiter) : []].filter(
|
|
3649
|
+
Boolean
|
|
3650
|
+
);
|
|
3651
|
+
return {
|
|
3652
|
+
...env,
|
|
3653
|
+
NODE_PATH: [...new Set(nodePathEntries)].join(path.delimiter)
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
function findUp(startDir, testFn) {
|
|
3657
|
+
const path = __require("node:path");
|
|
3658
|
+
let currentDir = startDir;
|
|
3659
|
+
while (true) {
|
|
3660
|
+
const result = testFn(currentDir);
|
|
3661
|
+
if (result !== void 0) return result;
|
|
3662
|
+
const parentDir = path.dirname(currentDir);
|
|
3663
|
+
if (parentDir === currentDir) return void 0;
|
|
3664
|
+
currentDir = parentDir;
|
|
3665
|
+
}
|
|
3666
|
+
}
|
|
3667
|
+
function findWorkspaceProtocolDir(startDir) {
|
|
3668
|
+
const fs = __require("node:fs");
|
|
3669
|
+
const path = __require("node:path");
|
|
3670
|
+
return findUp(startDir, (dir) => {
|
|
3671
|
+
const candidate = path.join(dir, "libs", "protocol");
|
|
3672
|
+
return fs.existsSync(path.join(candidate, "dist", "index.js")) ? candidate : void 0;
|
|
3673
|
+
});
|
|
3674
|
+
}
|
|
3675
|
+
function findInstalledProtocolPackageDir(startDir) {
|
|
3676
|
+
const fs = __require("node:fs");
|
|
3677
|
+
const path = __require("node:path");
|
|
3678
|
+
return findUp(startDir, (dir) => {
|
|
3679
|
+
const candidate = path.join(dir, "node_modules", "@frontmcp", "protocol");
|
|
3680
|
+
return fs.existsSync(path.join(candidate, "package.json")) ? candidate : void 0;
|
|
3681
|
+
});
|
|
3682
|
+
}
|
|
3683
|
+
function findWorkspaceNodeModulesDir(startDir) {
|
|
3684
|
+
const fs = __require("node:fs");
|
|
3685
|
+
const path = __require("node:path");
|
|
3686
|
+
return findUp(startDir, (dir) => {
|
|
3687
|
+
const candidate = path.join(dir, "node_modules");
|
|
3688
|
+
return fs.existsSync(candidate) ? candidate : void 0;
|
|
3689
|
+
});
|
|
3690
|
+
}
|
|
3488
3691
|
|
|
3489
3692
|
// libs/testing/src/perf/perf-test.ts
|
|
3490
3693
|
var currentConfig = {};
|
package/esm/setup.mjs
CHANGED
|
@@ -8,23 +8,13 @@ var __commonJS = (cb, mod) => function __require() {
|
|
|
8
8
|
|
|
9
9
|
// libs/testing/src/platform/platform-types.ts
|
|
10
10
|
function getPlatformMimeType(platform) {
|
|
11
|
-
return
|
|
11
|
+
return "text/html;profile=mcp-app";
|
|
12
12
|
}
|
|
13
13
|
function getToolCallMetaPrefixes(platform) {
|
|
14
|
-
|
|
15
|
-
case "openai":
|
|
16
|
-
return ["openai/"];
|
|
17
|
-
default:
|
|
18
|
-
return ["ui/"];
|
|
19
|
-
}
|
|
14
|
+
return ["ui/"];
|
|
20
15
|
}
|
|
21
16
|
function getForbiddenMetaPrefixes(platform) {
|
|
22
|
-
|
|
23
|
-
case "openai":
|
|
24
|
-
return ["ui/", "frontmcp/"];
|
|
25
|
-
default:
|
|
26
|
-
return ["openai/", "frontmcp/"];
|
|
27
|
-
}
|
|
17
|
+
return ["openai/", "frontmcp/"];
|
|
28
18
|
}
|
|
29
19
|
var init_platform_types = __esm({
|
|
30
20
|
"libs/testing/src/platform/platform-types.ts"() {
|
|
@@ -141,12 +131,12 @@ var init_ui_matchers = __esm({
|
|
|
141
131
|
};
|
|
142
132
|
}
|
|
143
133
|
const hasUiHtml = Boolean(meta["ui/html"]);
|
|
144
|
-
const hasOutputTemplate = Boolean(meta["openai/outputTemplate"]);
|
|
145
134
|
const hasMimeType = Boolean(meta["ui/mimeType"]);
|
|
146
|
-
const
|
|
135
|
+
const hasUiObject = Boolean(meta["ui"] && typeof meta["ui"] === "object");
|
|
136
|
+
const pass = hasUiHtml || hasMimeType || hasUiObject;
|
|
147
137
|
return {
|
|
148
138
|
pass,
|
|
149
|
-
message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html,
|
|
139
|
+
message: () => pass ? "Expected result not to have widget metadata" : "Expected _meta to have widget metadata (ui/html, ui/mimeType, or ui object)"
|
|
150
140
|
};
|
|
151
141
|
};
|
|
152
142
|
toHaveCssClass = function(received, className) {
|
|
@@ -309,14 +299,7 @@ var init_ui_matchers = __esm({
|
|
|
309
299
|
message: () => `Expected _meta to have MIME type "${expectedMimeType}" for platform "${platform}", but no _meta found`
|
|
310
300
|
};
|
|
311
301
|
}
|
|
312
|
-
|
|
313
|
-
switch (platform) {
|
|
314
|
-
case "openai":
|
|
315
|
-
mimeTypeKey = "openai/mimeType";
|
|
316
|
-
break;
|
|
317
|
-
default:
|
|
318
|
-
mimeTypeKey = "ui/mimeType";
|
|
319
|
-
}
|
|
302
|
+
const mimeTypeKey = "ui/mimeType";
|
|
320
303
|
const actualMimeType = meta[mimeTypeKey];
|
|
321
304
|
const pass = actualMimeType === expectedMimeType;
|
|
322
305
|
return {
|
|
@@ -332,14 +315,7 @@ var init_ui_matchers = __esm({
|
|
|
332
315
|
message: () => `Expected _meta to have platform HTML for "${platform}", but no _meta found`
|
|
333
316
|
};
|
|
334
317
|
}
|
|
335
|
-
|
|
336
|
-
switch (platform) {
|
|
337
|
-
case "openai":
|
|
338
|
-
htmlKey = "openai/html";
|
|
339
|
-
break;
|
|
340
|
-
default:
|
|
341
|
-
htmlKey = "ui/html";
|
|
342
|
-
}
|
|
318
|
+
const htmlKey = "ui/html";
|
|
343
319
|
const html = meta[htmlKey];
|
|
344
320
|
const pass = typeof html === "string" && html.length > 0;
|
|
345
321
|
return {
|
package/example-tools/index.d.ts
CHANGED
|
@@ -16,5 +16,5 @@
|
|
|
16
16
|
*/
|
|
17
17
|
export { BASIC_UI_TOOL_CONFIG, FULL_UI_TOOL_CONFIG, basicUIToolInputSchema, basicUIToolOutputSchema, fullUIToolInputSchema, fullUIToolOutputSchema, } from './tool-configs';
|
|
18
18
|
export { generateBasicUIToolOutput, generateFullUIToolOutput } from './tool-configs';
|
|
19
|
-
export { EXPECTED_OPENAI_TOOLS_LIST_META_KEYS, EXPECTED_OPENAI_TOOL_CALL_META_KEYS, EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS, EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS,
|
|
19
|
+
export { EXPECTED_OPENAI_TOOLS_LIST_META_KEYS, EXPECTED_OPENAI_TOOL_CALL_META_KEYS, EXPECTED_EXTAPPS_TOOLS_LIST_META_KEYS, EXPECTED_EXTAPPS_TOOL_CALL_META_KEYS, EXPECTED_GENERIC_TOOLS_LIST_META_KEYS, EXPECTED_GENERIC_TOOL_CALL_META_KEYS, } from './tool-configs';
|
|
20
20
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/example-tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAGrF,OAAO,EACL,oCAAoC,EACpC,mCAAmC,EACnC,qCAAqC,EACrC,oCAAoC,EACpC,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/example-tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EACL,oBAAoB,EACpB,mBAAmB,EACnB,sBAAsB,EACtB,uBAAuB,EACvB,qBAAqB,EACrB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,yBAAyB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAGrF,OAAO,EACL,oCAAoC,EACpC,mCAAmC,EACnC,qCAAqC,EACrC,oCAAoC,EACpC,qCAAqC,EACrC,oCAAoC,GACrC,MAAM,gBAAgB,CAAC"}
|
|
@@ -146,12 +146,14 @@ export declare function generateFullUIToolOutput(input: z.infer<typeof fullUIToo
|
|
|
146
146
|
};
|
|
147
147
|
/**
|
|
148
148
|
* Expected meta keys for OpenAI platform in tools/list response.
|
|
149
|
+
* OpenAI now uses the standard ui/* namespace like all other platforms.
|
|
149
150
|
*/
|
|
150
|
-
export declare const EXPECTED_OPENAI_TOOLS_LIST_META_KEYS: readonly ["
|
|
151
|
+
export declare const EXPECTED_OPENAI_TOOLS_LIST_META_KEYS: readonly ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
|
|
151
152
|
/**
|
|
152
153
|
* Expected meta keys for OpenAI platform in tools/call response.
|
|
154
|
+
* OpenAI now uses the standard ui/* namespace like all other platforms.
|
|
153
155
|
*/
|
|
154
|
-
export declare const EXPECTED_OPENAI_TOOL_CALL_META_KEYS: readonly ["
|
|
156
|
+
export declare const EXPECTED_OPENAI_TOOL_CALL_META_KEYS: readonly ["ui/html", "ui/mimeType", "ui/type"];
|
|
155
157
|
/**
|
|
156
158
|
* Expected meta keys for ext-apps platform in tools/list response (SEP-1865).
|
|
157
159
|
*/
|
|
@@ -170,12 +172,4 @@ export declare const EXPECTED_GENERIC_TOOLS_LIST_META_KEYS: readonly ["ui/resour
|
|
|
170
172
|
* Uses ui/* namespace only.
|
|
171
173
|
*/
|
|
172
174
|
export declare const EXPECTED_GENERIC_TOOL_CALL_META_KEYS: readonly ["ui/html", "ui/mimeType", "ui/type"];
|
|
173
|
-
/**
|
|
174
|
-
* @deprecated Use EXPECTED_GENERIC_TOOLS_LIST_META_KEYS instead
|
|
175
|
-
*/
|
|
176
|
-
export declare const EXPECTED_FRONTMCP_TOOLS_LIST_META_KEYS: readonly ["ui/resourceUri", "ui/mimeType", "ui/cdn", "ui/type"];
|
|
177
|
-
/**
|
|
178
|
-
* @deprecated Use EXPECTED_GENERIC_TOOL_CALL_META_KEYS instead
|
|
179
|
-
*/
|
|
180
|
-
export declare const EXPECTED_FRONTMCP_TOOL_CALL_META_KEYS: readonly ["ui/html", "ui/mimeType", "ui/type"];
|
|
181
175
|
//# sourceMappingURL=tool-configs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-configs.d.ts","sourceRoot":"","sources":["../../src/example-tools/tool-configs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;GAEG;AACH,eAAO,MAAM,sBAAsB;;iBAEjC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;QAM7B;;;WAGG;;;CASG,CAAC;AAMX;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;iBAGhC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;iBAKjC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;QAM5B;;WAEG;;QAqBH;;WAEG;;QAEH;;WAEG;;;;;QAKH;;WAEG;;;;;QAKH;;WAEG;;QAEH;;WAEG;;QAEH;;WAEG;;;CAGG,CAAC;AAMX;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC;;;EAKtF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC;;;;;EAWpF;AAMD
|
|
1
|
+
{"version":3,"file":"tool-configs.d.ts","sourceRoot":"","sources":["../../src/example-tools/tool-configs.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAMxB;;GAEG;AACH,eAAO,MAAM,sBAAsB;;iBAEjC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,uBAAuB;;;iBAGlC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;;;QAM7B;;;WAGG;;;CASG,CAAC;AAMX;;GAEG;AACH,eAAO,MAAM,qBAAqB;;;iBAGhC,CAAC;AAEH;;GAEG;AACH,eAAO,MAAM,sBAAsB;;;;;iBAKjC,CAAC;AAEH;;;GAGG;AACH,eAAO,MAAM,mBAAmB;;;;;;;;;;;;;;QAM5B;;WAEG;;QAqBH;;WAEG;;QAEH;;WAEG;;;;;QAKH;;WAEG;;;;;QAKH;;WAEG;;QAEH;;WAEG;;QAEH;;WAEG;;;CAGG,CAAC;AAMX;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC;;;EAKtF;AAED;;GAEG;AACH,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC;;;;;EAWpF;AAMD;;;GAGG;AACH,eAAO,MAAM,oCAAoC,iEAAkE,CAAC;AAEpH;;;GAGG;AACH,eAAO,MAAM,mCAAmC,gDAAiD,CAAC;AAElG;;GAEG;AACH,eAAO,MAAM,qCAAqC,iEAAkE,CAAC;AAErH;;GAEG;AACH,eAAO,MAAM,oCAAoC,gDAAiD,CAAC;AAEnG;;;GAGG;AACH,eAAO,MAAM,qCAAqC,iEAAkE,CAAC;AAErH;;;GAGG;AACH,eAAO,MAAM,oCAAoC,gDAAiD,CAAC"}
|