@skyramp/mcp 0.5.0-rc.1 → 0.5.0-rc.11
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/build/entitlements/entitlementGate.d.ts +8 -0
- package/build/entitlements/entitlementGate.js +57 -0
- package/build/entitlements/runCodes.d.ts +18 -0
- package/build/entitlements/runCodes.js +16 -0
- package/build/index.js +31 -2
- package/build/playwright/PlaywrightTraceService.d.ts +17 -0
- package/build/playwright/PlaywrightTraceService.js +29 -1
- package/build/playwright/dataSafetyConfig.d.ts +74 -0
- package/build/playwright/dataSafetyConfig.js +109 -0
- package/build/playwright/essentialTools.d.ts +1 -0
- package/build/playwright/essentialTools.js +44 -0
- package/build/playwright/registerPlaywrightTools.d.ts +36 -1
- package/build/playwright/registerPlaywrightTools.js +87 -43
- package/build/playwright/safeResolveDataSafety.d.ts +24 -0
- package/build/playwright/safeResolveDataSafety.js +33 -0
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.d.ts +1 -1
- package/build/prompts/initialize-workspace/initializeWorkspacePrompt.js +22 -196
- package/build/prompts/modularization/ui-test-modularization.d.ts +1 -0
- package/build/prompts/modularization/ui-test-modularization.js +9 -2
- package/build/services/FleetExecutionService.d.ts +87 -16
- package/build/services/FleetExecutionService.js +487 -48
- package/build/services/FleetRunRegistry.d.ts +49 -0
- package/build/services/FleetRunRegistry.js +178 -0
- package/build/services/containerEnv.js +8 -2
- package/build/services/fleetLimitConfirmation.d.ts +19 -0
- package/build/services/fleetLimitConfirmation.js +126 -0
- package/build/tool-phases.d.ts +14 -8
- package/build/tool-phases.js +88 -43
- package/build/tools/code-refactor/assertion-state.js +3 -1
- package/build/tools/code-refactor/codeReuseTool.js +2 -1
- package/build/tools/code-refactor/utils-verify-gates.js +1 -1
- package/build/tools/executeFleetTool.d.ts +6 -4
- package/build/tools/executeFleetTool.js +67 -23
- package/build/tools/fixErrorTool.js +2 -1
- package/build/tools/fleetLifecycleTools.d.ts +20 -0
- package/build/tools/fleetLifecycleTools.js +124 -0
- package/build/tools/generate-tests/generateContractRestTool.d.ts +2 -0
- package/build/tools/generate-tests/generateContractRestTool.js +12 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.d.ts +38 -0
- package/build/tools/generate-tests/generateIntegrationRestTool.js +7 -1
- package/build/tools/generate-tests/generateUILoadRestTool.d.ts +19 -1
- package/build/tools/generate-tests/generateUILoadRestTool.js +316 -50
- package/build/tools/test-management/registerTestPlanTool.js +22 -6
- package/build/tools/workspace/initScanWorkspaceTool.js +16 -5
- package/build/types/AssertionOutcome.d.ts +3 -0
- package/build/utils/assertion-verify/created-add-guard.d.ts +12 -0
- package/build/utils/assertion-verify/created-add-guard.js +173 -0
- package/build/utils/assertion-verify/index.d.ts +1 -0
- package/build/utils/assertion-verify/index.js +1 -0
- package/build/utils/assertion-verify/verify.d.ts +7 -3
- package/build/utils/assertion-verify/verify.js +9 -3
- package/build/utils/featureFlags.js +6 -3
- package/build/utils/repositorySlug.d.ts +14 -0
- package/build/utils/repositorySlug.js +46 -1
- package/build/utils/telemetry.d.ts +2 -0
- package/build/utils/telemetry.js +12 -0
- package/build/utils/utils-verify/action-key.d.ts +59 -2
- package/build/utils/utils-verify/action-key.js +162 -6
- package/build/utils/utils-verify/action-sites.d.ts +7 -0
- package/build/utils/utils-verify/action-sites.js +277 -35
- package/build/utils/utils-verify/call-sites.d.ts +8 -2
- package/build/utils/utils-verify/language-spec.js +1 -1
- package/build/utils/utils-verify/parse.d.ts +4 -0
- package/build/utils/utils-verify/parse.js +2 -1
- package/build/utils/versions.d.ts +3 -3
- package/build/utils/versions.js +1 -1
- package/build/workspace/workspace.d.ts +54 -120
- package/build/workspace/workspace.js +22 -46
- package/build/workspace/workspaceVerifier.d.ts +4 -10
- package/build/workspace/workspaceVerifier.js +6 -248
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +7 -6
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +5 -4
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +12 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetyGuard.js +822 -0
- package/node_modules/playwright/lib/mcp/skyramp/dataSafetySession.js +372 -0
- package/node_modules/playwright/lib/mcp/skyramp/exportTool.js +101 -8
- package/node_modules/playwright/lib/mcp/skyramp/finishTestSetupTool.js +45 -0
- package/node_modules/playwright/lib/mcp/skyramp/index.js +16 -0
- package/node_modules/playwright/lib/mcp/skyramp/recordingPhase.js +65 -0
- package/node_modules/playwright/lib/mcp/skyramp/skyRampImport.js +1 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +598 -127
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +85 -31
- package/node_modules/playwright/lib/mcp/test/skyRampExport.test.js +132 -5
- package/node_modules/playwright/node_modules/playwright-core/lib/cli/program.js +2 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/client/browserContext.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/injectedScriptSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/generated/pollingRecorderSource.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/protocol/validator.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/browserContext.js +17 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/codegen/skyramp/jsonlReader.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harRecorder.js +1 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/har/harTracer.js +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderRunner.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/recorderSignalProcessor.js +2 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder/skyramp/clickExpandedState.js +75 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/server/recorder.js +4 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/utils/isomorphic/volatileDate.js +85 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/{codeMirrorModule-Bou8kKzE.js → codeMirrorModule-BuhAdckO.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-COAHN430.js +809 -0
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{index.0MJW0uG7.js → index.7AU8h0PZ.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/index.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/{uiMode.GLqsx5cI.js → uiMode.Dl7smBJn.js} +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +2 -2
- package/node_modules/playwright/node_modules/playwright-core/package.json +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/cli/program.ts +5 -0
- package/node_modules/playwright/node_modules/playwright-core/src/client/browserContext.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/client/types.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/generated/injectedScriptSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/generated/pollingRecorderSource.ts +1 -1
- package/node_modules/playwright/node_modules/playwright-core/src/protocol/validator.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/browserContext.ts +25 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/codegen/skyramp/jsonlReader.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harRecorder.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/har/harTracer.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderRunner.ts +3 -1
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/recorderSignalProcessor.ts +1 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder/skyramp/clickExpandedState.ts +82 -0
- package/node_modules/playwright/node_modules/playwright-core/src/server/recorder.ts +2 -0
- package/node_modules/playwright/node_modules/playwright-core/src/utils/isomorphic/volatileDate.ts +137 -1
- package/node_modules/playwright/package.json +1 -1
- package/package.json +4 -4
- package/plugin/skills/enhance-assertions/reference/integration.md +1 -0
- package/plugin/skills/setup-testbot-target-sut/SKILL.md +17 -11
- package/plugin/skills/setup-testbot-target-sut/reference/deployed-environment.md +105 -0
- package/plugin/skills/setup-testbot-target-sut/reference/existing-tests.md +1 -1
- package/node_modules/playwright/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-DTp2-8Si.js +0 -809
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { McpServer } from "@modelcontextprotocol/server";
|
|
2
|
+
import { type RunCodes } from "./runCodes.js";
|
|
3
|
+
/**
|
|
4
|
+
* Makes every tool registered on `server` after this call check the run's
|
|
5
|
+
* entitlement codes before it runs. Without a run token it does nothing, so
|
|
6
|
+
* every tool behaves as before. Must be called before any tool is registered.
|
|
7
|
+
*/
|
|
8
|
+
export declare function installEntitlementGate(server: McpServer, readCodes?: () => Promise<RunCodes>, token?: string | undefined): void;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { readRunCodes } from "./runCodes.js";
|
|
2
|
+
import { NOT_GATED, TOOLS } from "../tool-phases.js";
|
|
3
|
+
const FEATURE_NAMES = {
|
|
4
|
+
analyze: "change analysis",
|
|
5
|
+
recommend: "test recommendations",
|
|
6
|
+
generate: "test generation",
|
|
7
|
+
execute: "test execution",
|
|
8
|
+
};
|
|
9
|
+
function refusal(text) {
|
|
10
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
11
|
+
}
|
|
12
|
+
/** Returns why `tool` may not run with these codes, or undefined if it may. */
|
|
13
|
+
function refusalReason(tool, code, run) {
|
|
14
|
+
if ("refused" in run) {
|
|
15
|
+
return `Tool ${tool} is not available: Skyramp could not confirm what this run is allowed to do. ${run.refused}`;
|
|
16
|
+
}
|
|
17
|
+
if (run.codes.has(code))
|
|
18
|
+
return undefined;
|
|
19
|
+
return `Tool ${tool} is not available: your Skyramp plan does not include ${FEATURE_NAMES[code]}.`;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Makes every tool registered on `server` after this call check the run's
|
|
23
|
+
* entitlement codes before it runs. Without a run token it does nothing, so
|
|
24
|
+
* every tool behaves as before. Must be called before any tool is registered.
|
|
25
|
+
*/
|
|
26
|
+
export function installEntitlementGate(server, readCodes = readRunCodes, token = process.env.SKYRAMP_RUN_TOKEN) {
|
|
27
|
+
if (!token?.trim())
|
|
28
|
+
return;
|
|
29
|
+
let codes;
|
|
30
|
+
const runCodes = () => (codes ??= readCodes()
|
|
31
|
+
.catch((err) => ({
|
|
32
|
+
refused: err instanceof Error ? err.message : String(err),
|
|
33
|
+
}))
|
|
34
|
+
.then((run) => {
|
|
35
|
+
// A refusal can be temporary, so the next tool call reads again.
|
|
36
|
+
if ("refused" in run)
|
|
37
|
+
codes = undefined;
|
|
38
|
+
return run;
|
|
39
|
+
}));
|
|
40
|
+
// Typed loosely: the gate forwards every argument as it came, whatever
|
|
41
|
+
// overload the tool used.
|
|
42
|
+
const target = server;
|
|
43
|
+
const register = target.registerTool.bind(target);
|
|
44
|
+
target.registerTool = (name, config, cb) => {
|
|
45
|
+
const code = TOOLS[name]?.entitlement;
|
|
46
|
+
if (code === NOT_GATED)
|
|
47
|
+
return register(name, config, cb);
|
|
48
|
+
const handler = cb;
|
|
49
|
+
return register(name, config, async (...args) => {
|
|
50
|
+
if (code === undefined) {
|
|
51
|
+
return refusal(`Tool ${name} is not available in this run.`);
|
|
52
|
+
}
|
|
53
|
+
const reason = refusalReason(name, code, await runCodes());
|
|
54
|
+
return reason ? refusal(reason) : handler(...args);
|
|
55
|
+
});
|
|
56
|
+
};
|
|
57
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export type RunCodes = {
|
|
2
|
+
codes: ReadonlySet<string>;
|
|
3
|
+
}
|
|
4
|
+
/** Core refused the run token; `refused` is its reason. */
|
|
5
|
+
| {
|
|
6
|
+
refused: string;
|
|
7
|
+
};
|
|
8
|
+
type Entitlements = {
|
|
9
|
+
codes: string[];
|
|
10
|
+
} | {
|
|
11
|
+
error: string;
|
|
12
|
+
};
|
|
13
|
+
/** The part of SkyrampClient this file uses. Core releases before getEntitlements lack it. */
|
|
14
|
+
export interface Core {
|
|
15
|
+
getEntitlements?: (repository: string) => Promise<Entitlements>;
|
|
16
|
+
}
|
|
17
|
+
export declare function readRunCodes(core?: Core, env?: NodeJS.ProcessEnv): Promise<RunCodes>;
|
|
18
|
+
export {};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The only file that may ask Skyramp core for the run's entitlement codes.
|
|
3
|
+
* Everything else in MCP reads them through `readRunCodes`, so a change on the
|
|
4
|
+
* core side changes this file and nothing else.
|
|
5
|
+
*/
|
|
6
|
+
import { SkyrampClient } from "@skyramp/skyramp";
|
|
7
|
+
import { runRepository } from "../utils/repositorySlug.js";
|
|
8
|
+
export async function readRunCodes(core = new SkyrampClient(), env = process.env) {
|
|
9
|
+
if (!core.getEntitlements) {
|
|
10
|
+
return { refused: "This Skyramp version does not support it yet." };
|
|
11
|
+
}
|
|
12
|
+
const answer = await core.getEntitlements(runRepository(env) ?? "");
|
|
13
|
+
return "error" in answer
|
|
14
|
+
? { refused: answer.error }
|
|
15
|
+
: { codes: new Set(answer.codes) };
|
|
16
|
+
}
|
package/build/index.js
CHANGED
|
@@ -7,9 +7,12 @@ import { registerTraceTool } from "./tools/trace/startTraceCollectionTool.js";
|
|
|
7
7
|
import { registerTraceStopTool } from "./tools/trace/stopTraceCollectionTool.js";
|
|
8
8
|
import { registerExecuteSkyrampTestTool } from "./tools/executeSkyrampTestTool.js";
|
|
9
9
|
import { registerExecuteFleetTool } from "./tools/executeFleetTool.js";
|
|
10
|
+
import { registerFleetLifecycleTools } from "./tools/fleetLifecycleTools.js";
|
|
11
|
+
import { fleetRuns } from "./services/FleetRunRegistry.js";
|
|
10
12
|
import { AUTH_PLACEHOLDER_TOKEN } from "./types/TestTypes.js";
|
|
11
13
|
import { AUTH_CONFLICT_ERROR_MSG } from "./prompts/test-recommendation/recommendationSections.js";
|
|
12
14
|
import { logger } from "./utils/logger.js";
|
|
15
|
+
import { installEntitlementGate } from "./entitlements/entitlementGate.js";
|
|
13
16
|
import { registerUITestTool } from "./tools/generate-tests/generateUIRestTool.js";
|
|
14
17
|
import { registerUILoadTestTool } from "./tools/generate-tests/generateUILoadRestTool.js";
|
|
15
18
|
import { registerSmokeTestTool } from "./tools/generate-tests/generateSmokeRestTool.js";
|
|
@@ -52,6 +55,7 @@ import { isTestbotEnabled, isLocalDevEnabled, isSkillsLoaded, } from "./utils/fe
|
|
|
52
55
|
import { findRoot, readTestIdAttribute } from "./utils/pom-scope/index.js";
|
|
53
56
|
import { discoverTestIdAttribute } from "./utils/pom-scope/testIdDiscovery.js";
|
|
54
57
|
import { registerPlaywrightTools, registerTraceRecordingPrompt, getPlaywrightTraceService, } from "./playwright/index.js";
|
|
58
|
+
import { resolveDataSafetyOptions, playwrightToolOptionsFor, } from "./playwright/dataSafetyConfig.js";
|
|
55
59
|
// One-click routing instructions — injected into MCP server instructions so agents
|
|
56
60
|
// know to delegate to the one-click tool rather than self-orchestrating.
|
|
57
61
|
// Suppressed in testbot mode where the testbot prompt drives orchestration.
|
|
@@ -155,6 +159,8 @@ async function createServer({ era }) {
|
|
|
155
159
|
8. The user can always override workspace defaults by explicitly specifying values in their request.
|
|
156
160
|
`,
|
|
157
161
|
});
|
|
162
|
+
// Before any tool registers, so every tool goes through the check.
|
|
163
|
+
installEntitlementGate(server);
|
|
158
164
|
// Register prompts
|
|
159
165
|
logger.info("Starting prompt registration process");
|
|
160
166
|
const prompts = [
|
|
@@ -239,6 +245,7 @@ async function createServer({ era }) {
|
|
|
239
245
|
registerLogoutTool,
|
|
240
246
|
registerExecuteSkyrampTestTool,
|
|
241
247
|
registerExecuteFleetTool,
|
|
248
|
+
registerFleetLifecycleTools,
|
|
242
249
|
registerTraceTool,
|
|
243
250
|
registerTraceStopTool,
|
|
244
251
|
];
|
|
@@ -253,6 +260,11 @@ async function createServer({ era }) {
|
|
|
253
260
|
infrastructureTools.forEach((registerTool) => registerTool(server));
|
|
254
261
|
// Register Playwright browser tools (trace recording via browser automation).
|
|
255
262
|
//
|
|
263
|
+
// PlaywrightTraceService (and the fork's network-layer data-safety guard it
|
|
264
|
+
// wraps) is constructed here, before any per-call `repositoryPath` exists,
|
|
265
|
+
// and `browser_navigate` itself takes no `repositoryPath` param, so
|
|
266
|
+
// process.cwd() is the only path data-safety config resolves from.
|
|
267
|
+
//
|
|
256
268
|
// Tell the capture which attribute this repository uses for test ids. Without
|
|
257
269
|
// it the capture assumes Playwright's `data-testid`, and a repo using anything
|
|
258
270
|
// else gets no test-id anchor and a positional xpath for every element — which
|
|
@@ -309,7 +321,13 @@ async function createServer({ era }) {
|
|
|
309
321
|
error: err,
|
|
310
322
|
});
|
|
311
323
|
}
|
|
312
|
-
|
|
324
|
+
// SKYR-4047: thread data-safety options; resolveDataSafetyOptions returns
|
|
325
|
+
// undefined in the testbot lane (SKYRAMP_DATA_SAFETY env) so the fork reads env.
|
|
326
|
+
const dataSafety = await resolveDataSafetyOptions(process.cwd());
|
|
327
|
+
await registerPlaywrightTools(server, {
|
|
328
|
+
...playwrightToolOptionsFor(dataSafety),
|
|
329
|
+
testIdAttribute,
|
|
330
|
+
});
|
|
313
331
|
})().catch((err) => {
|
|
314
332
|
logger.error("Failed to register Playwright tools", { error: err });
|
|
315
333
|
});
|
|
@@ -325,11 +343,18 @@ async function createServer({ era }) {
|
|
|
325
343
|
runInitTrigger();
|
|
326
344
|
return server;
|
|
327
345
|
}
|
|
346
|
+
// A crashing process must not stay alive anywhere near the full graceful
|
|
347
|
+
// shutdown's 30s grace — it still gives workers a moment to cancel, but caps
|
|
348
|
+
// it low enough that a crash can't hang the process.
|
|
349
|
+
const CRASH_CLEANUP_WAIT_MS = 3_000;
|
|
350
|
+
/** Interrupt running fleets (bounded wait) so an exit does not leave workers loading. */
|
|
351
|
+
const cancelFleets = () => fleetRuns.cancelAll(CRASH_CLEANUP_WAIT_MS).catch(() => { });
|
|
328
352
|
// Global error handlers for crash telemetry
|
|
329
353
|
process.on("uncaughtException", async (error) => {
|
|
330
354
|
// Don't send analytics for EPIPE errors
|
|
331
355
|
const isEpipeError = "code" in error && error.code === "EPIPE";
|
|
332
356
|
if (isEpipeError) {
|
|
357
|
+
await cancelFleets();
|
|
333
358
|
process.exit(0);
|
|
334
359
|
}
|
|
335
360
|
logger.critical("Uncaught exception - MCP server crashing", {
|
|
@@ -342,6 +367,7 @@ process.on("uncaughtException", async (error) => {
|
|
|
342
367
|
catch (telemetryError) {
|
|
343
368
|
logger.error("Failed to send crash telemetry", { telemetryError });
|
|
344
369
|
}
|
|
370
|
+
await cancelFleets();
|
|
345
371
|
process.exit(1);
|
|
346
372
|
});
|
|
347
373
|
// Handle unhandled promise rejections — without this handler Node.js 15+
|
|
@@ -351,6 +377,7 @@ process.on("unhandledRejection", async (reason) => {
|
|
|
351
377
|
const error = reason instanceof Error ? reason : new Error(String(reason));
|
|
352
378
|
// EPIPE means the client already disconnected — exit cleanly
|
|
353
379
|
if ("code" in error && error.code === "EPIPE") {
|
|
380
|
+
await cancelFleets();
|
|
354
381
|
process.exit(0);
|
|
355
382
|
}
|
|
356
383
|
// gRPC/Docker connectivity errors — log and survive.
|
|
@@ -397,7 +424,9 @@ async function main() {
|
|
|
397
424
|
const cleanupPlaywright = playwrightService
|
|
398
425
|
? playwrightService.shutdown().catch(() => { })
|
|
399
426
|
: Promise.resolve();
|
|
400
|
-
|
|
427
|
+
// A running fleet is interrupted so it can cancel its workers, not left loading them.
|
|
428
|
+
const cleanupFleets = fleetRuns.cancelAll().catch(() => { });
|
|
429
|
+
Promise.all([cleanupPlaywright, cleanupFleets]).finally(() => {
|
|
401
430
|
handle.close().finally(() => {
|
|
402
431
|
process.exit(0);
|
|
403
432
|
});
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* to bridge the module systems.
|
|
7
7
|
*/
|
|
8
8
|
import type { CallToolResult } from "@modelcontextprotocol/server";
|
|
9
|
+
import type { DataSafetyOptions } from "./dataSafetyConfig.js";
|
|
9
10
|
interface McpTool {
|
|
10
11
|
name: string;
|
|
11
12
|
description?: string;
|
|
@@ -25,6 +26,8 @@ export interface PlaywrightTraceOptions {
|
|
|
25
26
|
* against, so only this server can supply it.
|
|
26
27
|
*/
|
|
27
28
|
testIdAttribute?: string;
|
|
29
|
+
/** Data safety: the workspace section, for the fork to validate. */
|
|
30
|
+
dataSafety?: DataSafetyOptions;
|
|
28
31
|
}
|
|
29
32
|
export declare class PlaywrightTraceService {
|
|
30
33
|
private _backend;
|
|
@@ -35,6 +38,20 @@ export declare class PlaywrightTraceService {
|
|
|
35
38
|
isInitialized(): boolean;
|
|
36
39
|
getTools(): McpTool[];
|
|
37
40
|
callTool(name: string, args: Record<string, unknown>): Promise<CallToolResult>;
|
|
41
|
+
/**
|
|
42
|
+
* How long shutdown() waits for the fork's session-end teardown, so a hung
|
|
43
|
+
* target cannot hold the process forever. Teardown runs after a 5 s
|
|
44
|
+
* body-read settle, in up to 3 DELETE passes; each DELETE times out after
|
|
45
|
+
* 10 s, and a pass in which every DELETE timed out is not retried. Several
|
|
46
|
+
* hung DELETEs can still use up this bound. The fork writes the ledger after
|
|
47
|
+
* each deletion, so the ledger file is still correct when the bound runs out.
|
|
48
|
+
*/
|
|
49
|
+
static shutdownTeardownTimeoutMs: number;
|
|
50
|
+
/**
|
|
51
|
+
* The fork's serverClosed() starts data-safety teardown and returns at
|
|
52
|
+
* once. The caller exits the process right after this resolves, so wait
|
|
53
|
+
* for that work (bounded) — or the teardown DELETEs never reach the target.
|
|
54
|
+
*/
|
|
38
55
|
shutdown(): Promise<void>;
|
|
39
56
|
}
|
|
40
57
|
export {};
|
|
@@ -27,6 +27,7 @@ export class PlaywrightTraceService {
|
|
|
27
27
|
headless: this._options.headless,
|
|
28
28
|
outputDir: this._options.outputDir || process.cwd(),
|
|
29
29
|
testIdAttribute: this._options.testIdAttribute,
|
|
30
|
+
dataSafety: this._options.dataSafety,
|
|
30
31
|
});
|
|
31
32
|
// Initialize with a synthetic client info
|
|
32
33
|
await this._backend.initialize({
|
|
@@ -64,11 +65,38 @@ export class PlaywrightTraceService {
|
|
|
64
65
|
}
|
|
65
66
|
return this._backend.callTool(name, args);
|
|
66
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* How long shutdown() waits for the fork's session-end teardown, so a hung
|
|
70
|
+
* target cannot hold the process forever. Teardown runs after a 5 s
|
|
71
|
+
* body-read settle, in up to 3 DELETE passes; each DELETE times out after
|
|
72
|
+
* 10 s, and a pass in which every DELETE timed out is not retried. Several
|
|
73
|
+
* hung DELETEs can still use up this bound. The fork writes the ledger after
|
|
74
|
+
* each deletion, so the ledger file is still correct when the bound runs out.
|
|
75
|
+
*/
|
|
76
|
+
static shutdownTeardownTimeoutMs = 30_000;
|
|
77
|
+
/**
|
|
78
|
+
* The fork's serverClosed() starts data-safety teardown and returns at
|
|
79
|
+
* once. The caller exits the process right after this resolves, so wait
|
|
80
|
+
* for that work (bounded) — or the teardown DELETEs never reach the target.
|
|
81
|
+
*/
|
|
67
82
|
async shutdown() {
|
|
68
83
|
if (this._backend) {
|
|
69
|
-
this._backend
|
|
84
|
+
const backend = this._backend;
|
|
70
85
|
this._backend = null;
|
|
71
86
|
this._tools = [];
|
|
87
|
+
backend.serverClosed?.();
|
|
88
|
+
const pending = backend.autoExportPromise;
|
|
89
|
+
if (pending) {
|
|
90
|
+
let timer;
|
|
91
|
+
const bound = new Promise((resolve) => {
|
|
92
|
+
timer = setTimeout(() => {
|
|
93
|
+
logger.warning(`Data-safety teardown did not finish within ${PlaywrightTraceService.shutdownTeardownTimeoutMs} ms; exiting anyway`);
|
|
94
|
+
resolve();
|
|
95
|
+
}, PlaywrightTraceService.shutdownTeardownTimeoutMs);
|
|
96
|
+
});
|
|
97
|
+
await Promise.race([pending.catch(() => { }), bound]);
|
|
98
|
+
clearTimeout(timer);
|
|
99
|
+
}
|
|
72
100
|
logger.info("PlaywrightTraceService shut down");
|
|
73
101
|
}
|
|
74
102
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mcp-side data-safety config resolution + off-limits URL check.
|
|
3
|
+
*
|
|
4
|
+
* Resolution stack — which function a caller should reach for:
|
|
5
|
+
* - `resolveDataSafetyOptions` (this file): reads the workspace.yml
|
|
6
|
+
* `dataSafety` section, honoring the env-var precedence rule below.
|
|
7
|
+
* Lowest-level; used to build the fork's `options` argument.
|
|
8
|
+
* - `effectiveDataSafetyConfig` (this file): resolves through the fork's
|
|
9
|
+
* own `parseDataSafetyConfig` so the result matches exactly what the
|
|
10
|
+
* fork's network-layer guard will enforce. Can throw (unguarded require
|
|
11
|
+
* of the vendored fork).
|
|
12
|
+
* - `safeResolveDataSafety` (safeResolveDataSafety.ts): fail-open wrapper
|
|
13
|
+
* around `effectiveDataSafetyConfig`.
|
|
14
|
+
* - `resolveDataSafetyForRegistration` (registerPlaywrightTools.ts):
|
|
15
|
+
* fail-open wrapper for the enforcement lane specifically — resolves via
|
|
16
|
+
* `safeResolveDataSafety` and additionally normalizes an inert result
|
|
17
|
+
* (mode "off", no offLimits) to `null` for the navigate pre-check.
|
|
18
|
+
*
|
|
19
|
+
* The vendored Playwright fork's DataSafetyGuard/parseDataSafetyConfig read
|
|
20
|
+
* their config with precedence `options ?? env ?? inert`. Testbot resolves
|
|
21
|
+
* the full precedence chain (action input > workspace > auto-default) itself
|
|
22
|
+
* and ships the result via the SKYRAMP_DATA_SAFETY env var, which the fork
|
|
23
|
+
* reads directly. If mcp ALSO passed options derived from the workspace file
|
|
24
|
+
* in that lane, options would win over env and silently override testbot's
|
|
25
|
+
* resolution. So `resolveDataSafetyOptions` returns undefined whenever
|
|
26
|
+
* SKYRAMP_DATA_SAFETY is set — only the env-less lane (local/manual MCP
|
|
27
|
+
* usage, no testbot in the loop) reads the workspace section itself.
|
|
28
|
+
*
|
|
29
|
+
* The dataSafety section is read out of the raw YAML rather than through the
|
|
30
|
+
* full strict workspace schema (see workspace/workspace.ts), so an old or
|
|
31
|
+
* unrelated workspace.yml that fails full schema validation for reasons that
|
|
32
|
+
* have nothing to do with data safety cannot flip anyone to strict by
|
|
33
|
+
* accident. A PRESENT dataSafety section goes to the fork as written, and the
|
|
34
|
+
* fork's validateConfig checks each field on its own: a bad mode fails closed
|
|
35
|
+
* to strict, and a bad offLimits entry stops every tool call, so a bad field
|
|
36
|
+
* never drops the fields next to it. A workspace.yml that does not parse but
|
|
37
|
+
* has a top-level dataSafety key fails closed to strict.
|
|
38
|
+
*/
|
|
39
|
+
export type DataSafetyConfig = {
|
|
40
|
+
mode: "strict" | "readonly" | "off";
|
|
41
|
+
offLimits: string[];
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* The fork's `options` argument: the workspace.yml dataSafety section as
|
|
45
|
+
* written, unvalidated. The fork's parseDataSafetyConfig validates it.
|
|
46
|
+
*/
|
|
47
|
+
export type DataSafetyOptions = unknown;
|
|
48
|
+
/**
|
|
49
|
+
* Resolve mcp-side options for the trace backend. Returns undefined when
|
|
50
|
+
* SKYRAMP_DATA_SAFETY is set (testbot's resolution wins — see module
|
|
51
|
+
* docstring) or when no dataSafety signal exists in the workspace file.
|
|
52
|
+
* Only `mode` (absent means "off") and `offLimits` are passed on, so a field
|
|
53
|
+
* added to the section later cannot change what the fork enforces.
|
|
54
|
+
*/
|
|
55
|
+
export declare function resolveDataSafetyOptions(workspaceRoot: string): Promise<DataSafetyOptions | undefined>;
|
|
56
|
+
/** The config exactly as the fork's parseDataSafetyConfig will resolve it. */
|
|
57
|
+
export declare function effectiveDataSafetyConfig(workspaceRoot: string): Promise<DataSafetyConfig>;
|
|
58
|
+
/** offLimits check via the fork's own pure matcher so mcp and fork can never disagree. */
|
|
59
|
+
export declare function isOffLimitsUrl(url: string, config: DataSafetyConfig): boolean;
|
|
60
|
+
/**
|
|
61
|
+
* Maps a resolved data-safety config to the `options` argument
|
|
62
|
+
* `registerPlaywrightTools` expects, for the enforcement-lane wiring in
|
|
63
|
+
* index.ts: `resolveDataSafetyOptions(cwd).then(ds => registerPlaywrightTools(server, playwrightToolOptionsFor(ds)))`.
|
|
64
|
+
* Extracted (rather than an inline arrow at the call site) so the mapping is
|
|
65
|
+
* directly unit-testable without booting the McpServer — mirrors
|
|
66
|
+
* `resolveDataSafetyForRegistration`'s existing test approach
|
|
67
|
+
* (registerPlaywrightTools.dataSafety.test.ts). The return type is left
|
|
68
|
+
* structural (not `PlaywrightTraceOptions`) to avoid a circular import with
|
|
69
|
+
* PlaywrightTraceService.ts, which itself imports `DataSafetyConfig` from
|
|
70
|
+
* this module.
|
|
71
|
+
*/
|
|
72
|
+
export declare function playwrightToolOptionsFor(dataSafety: DataSafetyOptions | undefined): {
|
|
73
|
+
dataSafety: DataSafetyOptions;
|
|
74
|
+
} | undefined;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mcp-side data-safety config resolution + off-limits URL check.
|
|
3
|
+
*
|
|
4
|
+
* Resolution stack — which function a caller should reach for:
|
|
5
|
+
* - `resolveDataSafetyOptions` (this file): reads the workspace.yml
|
|
6
|
+
* `dataSafety` section, honoring the env-var precedence rule below.
|
|
7
|
+
* Lowest-level; used to build the fork's `options` argument.
|
|
8
|
+
* - `effectiveDataSafetyConfig` (this file): resolves through the fork's
|
|
9
|
+
* own `parseDataSafetyConfig` so the result matches exactly what the
|
|
10
|
+
* fork's network-layer guard will enforce. Can throw (unguarded require
|
|
11
|
+
* of the vendored fork).
|
|
12
|
+
* - `safeResolveDataSafety` (safeResolveDataSafety.ts): fail-open wrapper
|
|
13
|
+
* around `effectiveDataSafetyConfig`.
|
|
14
|
+
* - `resolveDataSafetyForRegistration` (registerPlaywrightTools.ts):
|
|
15
|
+
* fail-open wrapper for the enforcement lane specifically — resolves via
|
|
16
|
+
* `safeResolveDataSafety` and additionally normalizes an inert result
|
|
17
|
+
* (mode "off", no offLimits) to `null` for the navigate pre-check.
|
|
18
|
+
*
|
|
19
|
+
* The vendored Playwright fork's DataSafetyGuard/parseDataSafetyConfig read
|
|
20
|
+
* their config with precedence `options ?? env ?? inert`. Testbot resolves
|
|
21
|
+
* the full precedence chain (action input > workspace > auto-default) itself
|
|
22
|
+
* and ships the result via the SKYRAMP_DATA_SAFETY env var, which the fork
|
|
23
|
+
* reads directly. If mcp ALSO passed options derived from the workspace file
|
|
24
|
+
* in that lane, options would win over env and silently override testbot's
|
|
25
|
+
* resolution. So `resolveDataSafetyOptions` returns undefined whenever
|
|
26
|
+
* SKYRAMP_DATA_SAFETY is set — only the env-less lane (local/manual MCP
|
|
27
|
+
* usage, no testbot in the loop) reads the workspace section itself.
|
|
28
|
+
*
|
|
29
|
+
* The dataSafety section is read out of the raw YAML rather than through the
|
|
30
|
+
* full strict workspace schema (see workspace/workspace.ts), so an old or
|
|
31
|
+
* unrelated workspace.yml that fails full schema validation for reasons that
|
|
32
|
+
* have nothing to do with data safety cannot flip anyone to strict by
|
|
33
|
+
* accident. A PRESENT dataSafety section goes to the fork as written, and the
|
|
34
|
+
* fork's validateConfig checks each field on its own: a bad mode fails closed
|
|
35
|
+
* to strict, and a bad offLimits entry stops every tool call, so a bad field
|
|
36
|
+
* never drops the fields next to it. A workspace.yml that does not parse but
|
|
37
|
+
* has a top-level dataSafety key fails closed to strict.
|
|
38
|
+
*/
|
|
39
|
+
import fs from "fs";
|
|
40
|
+
import path from "path";
|
|
41
|
+
import { createRequire } from "module";
|
|
42
|
+
import yaml from "js-yaml";
|
|
43
|
+
import { logger } from "../utils/logger.js";
|
|
44
|
+
const require = createRequire(import.meta.url);
|
|
45
|
+
const TOP_LEVEL_DATA_SAFETY_KEY = /^["']?dataSafety["']?\s*:/m;
|
|
46
|
+
/**
|
|
47
|
+
* Resolve mcp-side options for the trace backend. Returns undefined when
|
|
48
|
+
* SKYRAMP_DATA_SAFETY is set (testbot's resolution wins — see module
|
|
49
|
+
* docstring) or when no dataSafety signal exists in the workspace file.
|
|
50
|
+
* Only `mode` (absent means "off") and `offLimits` are passed on, so a field
|
|
51
|
+
* added to the section later cannot change what the fork enforces.
|
|
52
|
+
*/
|
|
53
|
+
export async function resolveDataSafetyOptions(workspaceRoot) {
|
|
54
|
+
if (process.env.SKYRAMP_DATA_SAFETY?.trim())
|
|
55
|
+
return undefined;
|
|
56
|
+
const configPath = path.join(workspaceRoot, ".skyramp", "workspace.yml");
|
|
57
|
+
let text;
|
|
58
|
+
try {
|
|
59
|
+
text = fs.readFileSync(configPath, "utf8");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined; // no file: no signal
|
|
63
|
+
}
|
|
64
|
+
let raw;
|
|
65
|
+
try {
|
|
66
|
+
raw = yaml.load(text);
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (!TOP_LEVEL_DATA_SAFETY_KEY.test(text))
|
|
70
|
+
return undefined;
|
|
71
|
+
logger.warning("workspace.yml is not valid YAML and has a dataSafety section — failing closed to strict, with no offLimits", { error: error instanceof Error ? error.message : String(error) });
|
|
72
|
+
return { mode: "strict" };
|
|
73
|
+
}
|
|
74
|
+
const section = raw?.["dataSafety"];
|
|
75
|
+
if (section === undefined || section === null)
|
|
76
|
+
return undefined;
|
|
77
|
+
if (typeof section !== "object" || Array.isArray(section))
|
|
78
|
+
return section;
|
|
79
|
+
const { mode, offLimits } = section;
|
|
80
|
+
return { mode: mode === undefined ? "off" : mode, offLimits };
|
|
81
|
+
}
|
|
82
|
+
/** The config exactly as the fork's parseDataSafetyConfig will resolve it. */
|
|
83
|
+
export async function effectiveDataSafetyConfig(workspaceRoot) {
|
|
84
|
+
const options = await resolveDataSafetyOptions(workspaceRoot);
|
|
85
|
+
const { parseDataSafetyConfig } = require("playwright/lib/mcp/skyramp");
|
|
86
|
+
return parseDataSafetyConfig(options, process.env.SKYRAMP_DATA_SAFETY);
|
|
87
|
+
}
|
|
88
|
+
/** offLimits check via the fork's own pure matcher so mcp and fork can never disagree. */
|
|
89
|
+
export function isOffLimitsUrl(url, config) {
|
|
90
|
+
if (!config.offLimits.length)
|
|
91
|
+
return false;
|
|
92
|
+
const { matchesOffLimits } = require("playwright/lib/mcp/skyramp");
|
|
93
|
+
return matchesOffLimits(url, config.offLimits);
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Maps a resolved data-safety config to the `options` argument
|
|
97
|
+
* `registerPlaywrightTools` expects, for the enforcement-lane wiring in
|
|
98
|
+
* index.ts: `resolveDataSafetyOptions(cwd).then(ds => registerPlaywrightTools(server, playwrightToolOptionsFor(ds)))`.
|
|
99
|
+
* Extracted (rather than an inline arrow at the call site) so the mapping is
|
|
100
|
+
* directly unit-testable without booting the McpServer — mirrors
|
|
101
|
+
* `resolveDataSafetyForRegistration`'s existing test approach
|
|
102
|
+
* (registerPlaywrightTools.dataSafety.test.ts). The return type is left
|
|
103
|
+
* structural (not `PlaywrightTraceOptions`) to avoid a circular import with
|
|
104
|
+
* PlaywrightTraceService.ts, which itself imports `DataSafetyConfig` from
|
|
105
|
+
* this module.
|
|
106
|
+
*/
|
|
107
|
+
export function playwrightToolOptionsFor(dataSafety) {
|
|
108
|
+
return dataSafety !== undefined ? { dataSafety } : undefined;
|
|
109
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const ESSENTIAL_TOOLS: ReadonlySet<string>;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// Only expose essential browser tools to reduce LLM confusion.
|
|
2
|
+
// The full set (23 tools) overwhelms the LLM and causes it to use
|
|
3
|
+
// browser_evaluate/browser_run_code instead of the simpler tools.
|
|
4
|
+
//
|
|
5
|
+
// browser_blueprint is the only DOM Analyzer tool registered — it handles
|
|
6
|
+
// both full captures and same-URL deltas via the URL-cache (isFullCapture).
|
|
7
|
+
// browser_blueprint_diff, browser_sitemap_build, browser_sitemap_query fire
|
|
8
|
+
// 0 times in production evals and have no active call path in the testbot prompt.
|
|
9
|
+
//
|
|
10
|
+
// browser_widget_contract_lookup / browser_widget_contract_cache are
|
|
11
|
+
// intentionally excluded: the curated fingerprint library only covers
|
|
12
|
+
// test fixtures (not production widgets), and the LLM can handle custom
|
|
13
|
+
// widgets via snapshot-driven trial-and-error without the tool.
|
|
14
|
+
// The widgetType field on blueprint elements still signals "custom" to
|
|
15
|
+
// the agent — no guidance is lost.
|
|
16
|
+
export const ESSENTIAL_TOOLS = new Set([
|
|
17
|
+
"browser_navigate",
|
|
18
|
+
"browser_snapshot",
|
|
19
|
+
"browser_click",
|
|
20
|
+
"browser_type",
|
|
21
|
+
"browser_press_key",
|
|
22
|
+
"browser_select_option",
|
|
23
|
+
"browser_hover",
|
|
24
|
+
"browser_drag",
|
|
25
|
+
"browser_file_upload",
|
|
26
|
+
"browser_evaluate",
|
|
27
|
+
"browser_tabs",
|
|
28
|
+
"browser_navigate_back",
|
|
29
|
+
"browser_wait_for",
|
|
30
|
+
"browser_take_screenshot",
|
|
31
|
+
"browser_assert",
|
|
32
|
+
"browser_assert_hidden",
|
|
33
|
+
"browser_assert_api_request",
|
|
34
|
+
"browser_assert_table_cell",
|
|
35
|
+
"browser_mouse_action",
|
|
36
|
+
"browser_visual_snapshot",
|
|
37
|
+
"browser_gojs_add_node",
|
|
38
|
+
"browser_gojs_link",
|
|
39
|
+
"skyramp_export_zip",
|
|
40
|
+
"skyramp_load_trace",
|
|
41
|
+
"skyramp_finish_test_setup",
|
|
42
|
+
// DOM Analyzer: blueprint capture (capture-act-capture pattern)
|
|
43
|
+
"browser_blueprint",
|
|
44
|
+
]);
|
|
@@ -3,6 +3,41 @@
|
|
|
3
3
|
* tools in the Skyramp MCP server, delegating calls to PlaywrightTraceService.
|
|
4
4
|
*/
|
|
5
5
|
import { PlaywrightTraceService, type PlaywrightTraceOptions } from "./PlaywrightTraceService.js";
|
|
6
|
-
import type {
|
|
6
|
+
import type { DataSafetyConfig } from "./dataSafetyConfig.js";
|
|
7
|
+
import type { McpServer, CallToolResult } from "@modelcontextprotocol/server";
|
|
7
8
|
export declare function getPlaywrightTraceService(): PlaywrightTraceService | null;
|
|
9
|
+
/** Courtesy pre-check so the agent gets an immediate, readable refusal
|
|
10
|
+
* instead of a page-level network error. The fork's network layer remains
|
|
11
|
+
* the hard guarantee — this is UX, not enforcement — so a throw from the
|
|
12
|
+
* matcher (e.g. the vendored fork's require() resolving a partial install)
|
|
13
|
+
* must degrade to "not off-limits" rather than block navigation outright. */
|
|
14
|
+
export declare function precheckNavigate(url: string, config: DataSafetyConfig): string | null;
|
|
15
|
+
/**
|
|
16
|
+
* Pure regression guard for the per-call browser_navigate pre-check: the
|
|
17
|
+
* `tool.name === "browser_navigate" && config` gate, the params.url
|
|
18
|
+
* extraction, and the isError result shape, all extracted out of the tool
|
|
19
|
+
* handler closure so they can be exercised directly under Jest without a
|
|
20
|
+
* real McpServer/PlaywrightTraceService. The registration call site becomes
|
|
21
|
+
* `const refusal = maybeRefuseNavigate(...); if (refusal) return refusal;`.
|
|
22
|
+
* Returns undefined for every "let the call through" case: wrong tool, no
|
|
23
|
+
* url param, or no config (data safety not configured for this run).
|
|
24
|
+
*/
|
|
25
|
+
export declare function maybeRefuseNavigate(toolName: string, params: unknown, config: DataSafetyConfig | undefined): CallToolResult | undefined;
|
|
26
|
+
/**
|
|
27
|
+
* Resolves the data-safety pre-check config for `registerPlaywrightTools`
|
|
28
|
+
* and normalizes an inert result (mode "off" with
|
|
29
|
+
* no offLimits) to `null`. MUST go through `safeResolveDataSafety` — this
|
|
30
|
+
* runs after `_service.initialize()` has already succeeded, so an unguarded
|
|
31
|
+
* rejection here (e.g. the vendored fork's `require()` resolving a partial
|
|
32
|
+
* install) would abort registration of all 23 Playwright browser tools, not
|
|
33
|
+
* just the advisory pre-check. The inner try/catch is defense in depth in
|
|
34
|
+
* case `safeResolveDataSafety` itself ever regresses its no-throw contract —
|
|
35
|
+
* either way, an undefined/failed resolution degrades to "pre-check inert"
|
|
36
|
+
* (`null`), never a thrown error.
|
|
37
|
+
*
|
|
38
|
+
* Exported (rather than inlined) so it can be exercised directly under Jest
|
|
39
|
+
* without driving the McpServer-dependent registration flow — see
|
|
40
|
+
* `registerPlaywrightTools.dataSafety.test.ts`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function resolveDataSafetyForRegistration(cwd: string): Promise<DataSafetyConfig | null>;
|
|
8
43
|
export declare function registerPlaywrightTools(server: McpServer, options?: PlaywrightTraceOptions): Promise<void>;
|