Add SPA session validation and buglist, update migration docs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2025-12-03 21:28:08 +00:00
parent 9be3dfc14e
commit cff287e870
24169 changed files with 10223 additions and 7120 deletions

0
node_modules/playwright/lib/mcp/sdk/bundle.js generated vendored Normal file → Executable file
View File

4
node_modules/playwright/lib/mcp/sdk/exports.js generated vendored Normal file → Executable file
View File

@@ -20,13 +20,11 @@ __reExport(exports_exports, require("./proxyBackend"), module.exports);
__reExport(exports_exports, require("./server"), module.exports);
__reExport(exports_exports, require("./tool"), module.exports);
__reExport(exports_exports, require("./http"), module.exports);
__reExport(exports_exports, require("./mdb"), module.exports);
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
...require("./inProcessTransport"),
...require("./proxyBackend"),
...require("./server"),
...require("./tool"),
...require("./http"),
...require("./mdb")
...require("./http")
});

11
node_modules/playwright/lib/mcp/sdk/http.js generated vendored Normal file → Executable file
View File

@@ -67,11 +67,12 @@ function httpAddressToString(address) {
resolvedHost = "localhost";
return `http://${resolvedHost}:${resolvedPort}`;
}
async function installHttpTransport(httpServer, serverBackendFactory, allowedHosts) {
async function installHttpTransport(httpServer, serverBackendFactory, unguessableUrl, allowedHosts) {
const url = httpAddressToString(httpServer.address());
const host = new URL(url).host;
allowedHosts = (allowedHosts || [host]).map((h) => h.toLowerCase());
const allowAnyHost = allowedHosts.includes("*");
const pathPrefix = unguessableUrl ? `/${import_crypto.default.randomUUID()}` : "";
const sseSessions = /* @__PURE__ */ new Map();
const streamableSessions = /* @__PURE__ */ new Map();
httpServer.on("request", async (req, res) => {
@@ -86,7 +87,12 @@ async function installHttpTransport(httpServer, serverBackendFactory, allowedHos
return res.end("Access is only allowed at " + allowedHosts.join(", "));
}
}
const url2 = new URL(`http://localhost${req.url}`);
if (!req.url?.startsWith(pathPrefix)) {
res.statusCode = 404;
return res.end("Not found");
}
const path = req.url?.slice(pathPrefix.length);
const url2 = new URL(`http://localhost${path}`);
if (url2.pathname === "/killkillkill" && req.method === "GET") {
res.statusCode = 200;
res.end("Killing process");
@@ -98,6 +104,7 @@ async function installHttpTransport(httpServer, serverBackendFactory, allowedHos
else
await handleStreamable(serverBackendFactory, req, res, streamableSessions);
});
return `${url}${pathPrefix}`;
}
async function handleSSE(serverBackendFactory, req, res, url, sessions) {
if (req.method === "POST") {

0
node_modules/playwright/lib/mcp/sdk/inProcessTransport.js generated vendored Normal file → Executable file
View File

View File

@@ -1,208 +0,0 @@
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var mdb_exports = {};
__export(mdb_exports, {
MDBBackend: () => MDBBackend,
runMainBackend: () => runMainBackend,
runOnPauseBackendLoop: () => runOnPauseBackendLoop
});
module.exports = __toCommonJS(mdb_exports);
var import_utilsBundle = require("playwright-core/lib/utilsBundle");
var import_utils = require("playwright-core/lib/utils");
var import_tool = require("./tool");
var mcpBundle = __toESM(require("./bundle"));
var mcpServer = __toESM(require("./server"));
var mcpHttp = __toESM(require("./http"));
const mdbDebug = (0, import_utilsBundle.debug)("pw:mcp:mdb");
const errorsDebug = (0, import_utilsBundle.debug)("pw:mcp:errors");
const z = mcpBundle.z;
class MDBBackend {
constructor(mainBackend) {
this._progress = [];
this._mainBackend = mainBackend;
this._progressCallback = (params) => {
if (params.message)
this._progress.push({ type: "text", text: params.message });
};
}
async initialize(server, clientInfo) {
if (!this._clientInfo) {
this._clientInfo = clientInfo;
await this._mainBackend.initialize?.(server, clientInfo);
}
}
async listTools() {
return await this._mainBackend.listTools();
}
async callTool(name, args) {
if (name === pushToolsSchema.name) {
await this._createOnPauseClient(pushToolsSchema.inputSchema.parse(args || {}));
return { content: [{ type: "text", text: "Tools pushed" }] };
}
if (this._onPauseClient?.tools.find((tool) => tool.name === name)) {
const result2 = await this._onPauseClient.client.callTool({
name,
arguments: args
});
await this._mainBackend.afterCallTool?.(name, args, result2);
return result2;
}
await this._onPauseClient?.transport.terminateSession().catch(errorsDebug);
await this._onPauseClient?.client.close().catch(errorsDebug);
this._onPauseClient = void 0;
const resultPromise = new import_utils.ManualPromise();
const interruptPromise = new import_utils.ManualPromise();
this._interruptPromise = interruptPromise;
this._mainBackend.callTool(name, args, this._progressCallback).then((result2) => {
resultPromise.resolve(result2);
}).catch((e) => {
resultPromise.resolve({ content: [{ type: "text", text: String(e) }], isError: true });
});
const result = await Promise.race([interruptPromise, resultPromise]);
if (interruptPromise.isDone())
mdbDebug("client call intercepted", result);
else
mdbDebug("client call result", result);
result.content.unshift(...this._progress);
this._progress.length = 0;
return result;
}
async _createOnPauseClient(params) {
if (this._onPauseClient)
await this._onPauseClient.client.close().catch(errorsDebug);
this._onPauseClient = await this._createClient(params.mcpUrl);
this._interruptPromise?.resolve({
content: [{
type: "text",
text: params.introMessage || ""
}]
});
this._interruptPromise = void 0;
}
async _createClient(url) {
const client = new mcpBundle.Client({ name: "Interrupting client", version: "0.0.0" }, { capabilities: { roots: {} } });
client.setRequestHandler(mcpBundle.ListRootsRequestSchema, () => ({ roots: this._clientInfo?.roots || [] }));
client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
client.setNotificationHandler(mcpBundle.ProgressNotificationSchema, (notification) => {
if (notification.method === "notifications/progress") {
const { message } = notification.params;
if (message)
this._progress.push({ type: "text", text: message });
}
});
const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(url));
await client.connect(transport);
const { tools } = await client.listTools();
return { client, tools, transport };
}
}
const pushToolsSchema = (0, import_tool.defineToolSchema)({
name: "mdb_push_tools",
title: "Push MCP tools to the tools stack",
description: "Push MCP tools to the tools stack",
inputSchema: z.object({
mcpUrl: z.string(),
introMessage: z.string().optional()
}),
type: "readOnly"
});
async function runMainBackend(backendFactory, options) {
const mdbBackend = new MDBBackend(backendFactory.create());
const factory = {
...backendFactory,
create: () => mdbBackend
};
const url = await startAsHttp(factory, { port: options?.port || 0 });
process.env.PLAYWRIGHT_DEBUGGER_MCP = url;
if (options?.port !== void 0)
return url;
await mcpServer.connect(factory, new mcpBundle.StdioServerTransport(), false);
}
async function runOnPauseBackendLoop(backend, introMessage) {
const wrappedBackend = new ServerBackendWithCloseListener(backend);
const factory = {
name: "on-pause-backend",
nameInConfig: "on-pause-backend",
version: "0.0.0",
create: () => wrappedBackend
};
const httpServer = await mcpHttp.startHttpServer({ port: 0 });
await mcpHttp.installHttpTransport(httpServer, factory);
const url = mcpHttp.httpAddressToString(httpServer.address());
const client = new mcpBundle.Client({ name: "Pushing client", version: "0.0.0" });
client.setRequestHandler(mcpBundle.PingRequestSchema, () => ({}));
const transport = new mcpBundle.StreamableHTTPClientTransport(new URL(process.env.PLAYWRIGHT_DEBUGGER_MCP));
await client.connect(transport);
const pushToolsResult = await client.callTool({
name: pushToolsSchema.name,
arguments: {
mcpUrl: url,
introMessage
}
});
if (pushToolsResult.isError)
errorsDebug("Failed to push tools", pushToolsResult.content);
await transport.terminateSession();
await client.close();
await wrappedBackend.waitForClosed();
httpServer.close();
}
async function startAsHttp(backendFactory, options) {
const httpServer = await mcpHttp.startHttpServer(options);
await mcpHttp.installHttpTransport(httpServer, backendFactory);
return mcpHttp.httpAddressToString(httpServer.address());
}
class ServerBackendWithCloseListener {
constructor(backend) {
this._serverClosedPromise = new import_utils.ManualPromise();
this._backend = backend;
}
async initialize(server, clientInfo) {
await this._backend.initialize?.(server, clientInfo);
}
async listTools() {
return this._backend.listTools();
}
async callTool(name, args, progress) {
return this._backend.callTool(name, args, progress);
}
serverClosed(server) {
this._backend.serverClosed?.(server);
this._serverClosedPromise.resolve();
}
async waitForClosed() {
await this._serverClosedPromise;
}
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
MDBBackend,
runMainBackend,
runOnPauseBackendLoop
});

2
node_modules/playwright/lib/mcp/sdk/proxyBackend.js generated vendored Normal file → Executable file
View File

@@ -40,7 +40,7 @@ class ProxyBackend {
this._mcpProviders = mcpProviders;
this._contextSwitchTool = this._defineContextSwitchTool();
}
async initialize(server, clientInfo) {
async initialize(clientInfo) {
this._clientInfo = clientInfo;
}
async listTools() {

18
node_modules/playwright/lib/mcp/sdk/server.js generated vendored Normal file → Executable file
View File

@@ -41,6 +41,7 @@ var mcpBundle = __toESM(require("./bundle"));
var import_http = require("./http");
var import_inProcessTransport = require("./inProcessTransport");
const serverDebug = (0, import_utilsBundle.debug)("pw:mcp:server");
const serverDebugResponse = (0, import_utilsBundle.debug)("pw:mcp:server:response");
async function connect(factory, transport, runHeartbeat) {
const server = createServer(factory.name, factory.version, factory.create(), runHeartbeat);
await server.connect(transport);
@@ -81,7 +82,10 @@ function createServer(name, version, backend, runHeartbeat) {
if (!initializePromise)
initializePromise = initializeServer(server, backend, runHeartbeat);
await initializePromise;
return mergeTextParts(await backend.callTool(request.params.name, request.params.arguments || {}, progress));
const toolResult = await backend.callTool(request.params.name, request.params.arguments || {}, progress);
const mergedResult = mergeTextParts(toolResult);
serverDebugResponse("callResult", mergedResult);
return mergedResult;
} catch (error) {
return {
content: [{ type: "text", text: "### Result\n" + String(error) }],
@@ -108,7 +112,7 @@ const initializeServer = async (server, backend, runHeartbeat) => {
roots: clientRoots,
timestamp: Date.now()
};
await backend.initialize?.(server, clientInfo);
await backend.initialize?.(clientInfo);
if (runHeartbeat)
startHeartbeat(server);
};
@@ -138,8 +142,7 @@ async function start(serverBackendFactory, options) {
return;
}
const httpServer = await (0, import_http.startHttpServer)(options);
const url = (0, import_http.httpAddressToString)(httpServer.address());
await (0, import_http.installHttpTransport)(httpServer, serverBackendFactory, options.allowedHosts);
const url = await (0, import_http.installHttpTransport)(httpServer, serverBackendFactory, false, options.allowedHosts);
const mcpConfig = { mcpServers: {} };
mcpConfig.mcpServers[serverBackendFactory.nameInConfig] = {
url: `${url}/mcp`
@@ -157,7 +160,12 @@ function firstRootPath(clientInfo) {
return void 0;
const firstRootUri = clientInfo.roots[0]?.uri;
const url = firstRootUri ? new URL(firstRootUri) : void 0;
return url ? (0, import_url.fileURLToPath)(url) : void 0;
try {
return url ? (0, import_url.fileURLToPath)(url) : void 0;
} catch (error) {
serverDebug(error);
return void 0;
}
}
function mergeTextParts(result) {
const content = [];

8
node_modules/playwright/lib/mcp/sdk/tool.js generated vendored Normal file → Executable file
View File

@@ -23,16 +23,12 @@ __export(tool_exports, {
});
module.exports = __toCommonJS(tool_exports);
var import_bundle = require("../sdk/bundle");
const typesWithIntent = ["action", "assertion", "input"];
function toMcpTool(tool, options) {
const inputSchema = options?.addIntent && typesWithIntent.includes(tool.type) ? tool.inputSchema.extend({
intent: import_bundle.z.string().describe("The intent of the call, for example the test step description plan idea")
}) : tool.inputSchema;
function toMcpTool(tool) {
const readOnly = tool.type === "readOnly" || tool.type === "assertion";
return {
name: tool.name,
description: tool.description,
inputSchema: (0, import_bundle.zodToJsonSchema)(inputSchema, { strictUnions: true }),
inputSchema: (0, import_bundle.zodToJsonSchema)(tool.inputSchema, { strictUnions: true }),
annotations: {
title: tool.title,
readOnlyHint: readOnly,