@letsrunit/mcp-server 0.14.2 → 0.14.4
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/dist/chunk-XJCBPTOU.js +72 -0
- package/dist/chunk-XJCBPTOU.js.map +1 -0
- package/dist/index.js +4 -97
- package/dist/index.js.map +1 -1
- package/dist/{tools-DVAZVHS6.js → tools-CNEENZR2.js} +51 -18
- package/dist/tools-CNEENZR2.js.map +1 -0
- package/package.json +8 -8
- package/src/bootstrap.ts +16 -55
- package/src/index.ts +4 -3
- package/src/tools/diagnostics.ts +17 -4
- package/src/utility/support.ts +46 -16
- package/dist/tools-DVAZVHS6.js.map +0 -1
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import * as __m from 'node:module';
|
|
3
|
+
import { spawnSync } from 'child_process';
|
|
4
|
+
import { realpathSync } from 'fs';
|
|
5
|
+
import { createRequire } from 'module';
|
|
6
|
+
import { resolve } from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
|
|
9
|
+
__m.createRequire(import.meta.url);
|
|
10
|
+
function resolveProjectRoot() {
|
|
11
|
+
return resolve(process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd());
|
|
12
|
+
}
|
|
13
|
+
function resolveFromProject(moduleId, projectRoot) {
|
|
14
|
+
try {
|
|
15
|
+
const req = createRequire(resolve(projectRoot, "package.json"));
|
|
16
|
+
return req.resolve(moduleId);
|
|
17
|
+
} catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function toRealpath(path) {
|
|
22
|
+
if (!path) return null;
|
|
23
|
+
try {
|
|
24
|
+
return realpathSync(path);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function sameEntrypoint(a, b) {
|
|
30
|
+
if (!a || !b) return false;
|
|
31
|
+
return a === b;
|
|
32
|
+
}
|
|
33
|
+
function decideHandoff(currentEntrypointPath, projectEntrypointPath, isBootstrapped) {
|
|
34
|
+
if (!projectEntrypointPath) {
|
|
35
|
+
return { shouldHandoff: false, runtimeMode: "standalone" };
|
|
36
|
+
}
|
|
37
|
+
if (sameEntrypoint(currentEntrypointPath, projectEntrypointPath)) {
|
|
38
|
+
return { shouldHandoff: false, runtimeMode: "project" };
|
|
39
|
+
}
|
|
40
|
+
if (isBootstrapped) {
|
|
41
|
+
return { shouldHandoff: false, runtimeMode: "standalone" };
|
|
42
|
+
}
|
|
43
|
+
return { shouldHandoff: true, runtimeMode: "project" };
|
|
44
|
+
}
|
|
45
|
+
function runProjectLocalServer(projectEntrypointPath) {
|
|
46
|
+
const result = spawnSync(process.execPath, [projectEntrypointPath, ...process.argv.slice(2)], {
|
|
47
|
+
stdio: "inherit",
|
|
48
|
+
env: {
|
|
49
|
+
...process.env,
|
|
50
|
+
LETSRUNIT_MCP_BOOTSTRAPPED: "1",
|
|
51
|
+
LETSRUNIT_MCP_RUNTIME_MODE: "project"
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
if (result.error) throw result.error;
|
|
55
|
+
process.exit(result.status ?? 1);
|
|
56
|
+
}
|
|
57
|
+
function bootstrapProjectServer() {
|
|
58
|
+
const projectRoot = resolveProjectRoot();
|
|
59
|
+
const isBootstrapped = process.env.LETSRUNIT_MCP_BOOTSTRAPPED === "1";
|
|
60
|
+
const currentEntryPath = toRealpath(fileURLToPath(import.meta.url));
|
|
61
|
+
const projectEntryPath = toRealpath(resolveFromProject("@letsrunit/mcp-server", projectRoot));
|
|
62
|
+
const decision = decideHandoff(currentEntryPath, projectEntryPath, isBootstrapped);
|
|
63
|
+
if (decision.shouldHandoff && projectEntryPath) {
|
|
64
|
+
runProjectLocalServer(projectEntryPath);
|
|
65
|
+
}
|
|
66
|
+
process.env.LETSRUNIT_MCP_RUNTIME_MODE = decision.runtimeMode;
|
|
67
|
+
return decision.runtimeMode;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export { bootstrapProjectServer, decideHandoff };
|
|
71
|
+
//# sourceMappingURL=chunk-XJCBPTOU.js.map
|
|
72
|
+
//# sourceMappingURL=chunk-XJCBPTOU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bootstrap.ts"],"names":[],"mappings":"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","file":"chunk-XJCBPTOU.js","sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { realpathSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nexport type McpRuntimeMode = 'project' | 'standalone';\n\nexport type HandoffDecision = {\n shouldHandoff: boolean;\n runtimeMode: McpRuntimeMode;\n};\n\nfunction resolveProjectRoot(): string {\n return resolve(process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd());\n}\n\nfunction resolveFromProject(moduleId: string, projectRoot: string): string | null {\n try {\n const req = createRequire(resolve(projectRoot, 'package.json'));\n return req.resolve(moduleId);\n } catch {\n return null;\n }\n}\n\nfunction toRealpath(path: string | null): string | null {\n if (!path) return null;\n try {\n return realpathSync(path);\n } catch {\n return null;\n }\n}\n\nfunction sameEntrypoint(a: string | null, b: string | null): boolean {\n if (!a || !b) return false;\n return a === b;\n}\n\nexport function decideHandoff(\n currentEntrypointPath: string | null,\n projectEntrypointPath: string | null,\n isBootstrapped: boolean,\n): HandoffDecision {\n if (!projectEntrypointPath) {\n return { shouldHandoff: false, runtimeMode: 'standalone' };\n }\n\n if (sameEntrypoint(currentEntrypointPath, projectEntrypointPath)) {\n return { shouldHandoff: false, runtimeMode: 'project' };\n }\n\n if (isBootstrapped) {\n return { shouldHandoff: false, runtimeMode: 'standalone' };\n }\n\n return { shouldHandoff: true, runtimeMode: 'project' };\n}\n\nfunction runProjectLocalServer(projectEntrypointPath: string): never {\n const result = spawnSync(process.execPath, [projectEntrypointPath, ...process.argv.slice(2)], {\n stdio: 'inherit',\n env: {\n ...process.env,\n LETSRUNIT_MCP_BOOTSTRAPPED: '1',\n LETSRUNIT_MCP_RUNTIME_MODE: 'project',\n },\n });\n\n if (result.error) throw result.error;\n process.exit(result.status ?? 1);\n}\n\nexport function bootstrapProjectServer(): McpRuntimeMode {\n const projectRoot = resolveProjectRoot();\n const isBootstrapped = process.env.LETSRUNIT_MCP_BOOTSTRAPPED === '1';\n\n const currentEntryPath = toRealpath(fileURLToPath(import.meta.url));\n const projectEntryPath = toRealpath(resolveFromProject('@letsrunit/mcp-server', projectRoot));\n\n const decision = decideHandoff(currentEntryPath, projectEntryPath, isBootstrapped);\n\n if (decision.shouldHandoff && projectEntryPath) {\n runProjectLocalServer(projectEntryPath);\n }\n\n process.env.LETSRUNIT_MCP_RUNTIME_MODE = decision.runtimeMode;\n return decision.runtimeMode;\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,104 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as __m from 'node:module';
|
|
3
|
-
import {
|
|
3
|
+
import { bootstrapProjectServer } from './chunk-XJCBPTOU.js';
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
-
import { spawnSync } from 'child_process';
|
|
7
|
-
import { realpathSync, existsSync, readFileSync } from 'fs';
|
|
8
|
-
import { resolve, dirname } from 'path';
|
|
9
6
|
|
|
10
7
|
__m.createRequire(import.meta.url);
|
|
11
|
-
|
|
12
|
-
return resolve(process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd());
|
|
13
|
-
}
|
|
14
|
-
function resolveFromProject(moduleId, projectRoot) {
|
|
15
|
-
try {
|
|
16
|
-
const req = createRequire(resolve(projectRoot, "package.json"));
|
|
17
|
-
return req.resolve(moduleId);
|
|
18
|
-
} catch {
|
|
19
|
-
return null;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
function findPackageJsonPath(entryPath) {
|
|
23
|
-
if (!entryPath) return null;
|
|
24
|
-
let dir = dirname(entryPath);
|
|
25
|
-
while (dir !== dirname(dir)) {
|
|
26
|
-
const packageJsonPath = resolve(dir, "package.json");
|
|
27
|
-
if (existsSync(packageJsonPath)) return packageJsonPath;
|
|
28
|
-
dir = dirname(dir);
|
|
29
|
-
}
|
|
30
|
-
return null;
|
|
31
|
-
}
|
|
32
|
-
function toRealpath(path) {
|
|
33
|
-
if (!path) return null;
|
|
34
|
-
try {
|
|
35
|
-
return realpathSync(path);
|
|
36
|
-
} catch {
|
|
37
|
-
return null;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
function samePackageRoot(a, b) {
|
|
41
|
-
if (!a || !b) return false;
|
|
42
|
-
return dirname(a) === dirname(b);
|
|
43
|
-
}
|
|
44
|
-
function resolveBinPath(packageJsonPath) {
|
|
45
|
-
const text = readFileSync(packageJsonPath, "utf8");
|
|
46
|
-
const pkg = JSON.parse(text);
|
|
47
|
-
if (typeof pkg.bin === "string") {
|
|
48
|
-
return resolve(dirname(packageJsonPath), pkg.bin);
|
|
49
|
-
}
|
|
50
|
-
if (pkg.bin && typeof pkg.bin === "object") {
|
|
51
|
-
const named = pkg.bin["letsrunit-mcp"];
|
|
52
|
-
const first = Object.values(pkg.bin)[0];
|
|
53
|
-
const bin = named ?? first;
|
|
54
|
-
if (typeof bin === "string") {
|
|
55
|
-
return resolve(dirname(packageJsonPath), bin);
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
throw new Error(`Unable to resolve mcp bin from ${packageJsonPath}`);
|
|
59
|
-
}
|
|
60
|
-
function decideHandoff(currentPackageJsonPath, projectPackageJsonPath, isBootstrapped) {
|
|
61
|
-
if (!projectPackageJsonPath) {
|
|
62
|
-
return { shouldHandoff: false, runtimeMode: "standalone" };
|
|
63
|
-
}
|
|
64
|
-
if (samePackageRoot(currentPackageJsonPath, projectPackageJsonPath)) {
|
|
65
|
-
return { shouldHandoff: false, runtimeMode: "project" };
|
|
66
|
-
}
|
|
67
|
-
if (isBootstrapped) {
|
|
68
|
-
return { shouldHandoff: false, runtimeMode: "standalone" };
|
|
69
|
-
}
|
|
70
|
-
return { shouldHandoff: true, runtimeMode: "project" };
|
|
71
|
-
}
|
|
72
|
-
function runProjectLocalServer(projectPackageJsonPath) {
|
|
73
|
-
const entry = resolveBinPath(projectPackageJsonPath);
|
|
74
|
-
const result = spawnSync(process.execPath, [entry, ...process.argv.slice(2)], {
|
|
75
|
-
stdio: "inherit",
|
|
76
|
-
env: {
|
|
77
|
-
...process.env,
|
|
78
|
-
LETSRUNIT_MCP_BOOTSTRAPPED: "1",
|
|
79
|
-
LETSRUNIT_MCP_RUNTIME_MODE: "project"
|
|
80
|
-
}
|
|
81
|
-
});
|
|
82
|
-
if (result.error) throw result.error;
|
|
83
|
-
process.exit(result.status ?? 1);
|
|
84
|
-
}
|
|
85
|
-
function bootstrapProjectServer() {
|
|
86
|
-
const projectRoot = resolveProjectRoot();
|
|
87
|
-
const isBootstrapped = process.env.LETSRUNIT_MCP_BOOTSTRAPPED === "1";
|
|
88
|
-
const currentReq = createRequire(import.meta.url);
|
|
89
|
-
const currentPackageJsonPath = toRealpath(currentReq.resolve("../package.json"));
|
|
90
|
-
const projectEntryPath = resolveFromProject("@letsrunit/mcp-server", projectRoot);
|
|
91
|
-
const projectPackageJsonPath = toRealpath(findPackageJsonPath(projectEntryPath));
|
|
92
|
-
const decision = decideHandoff(currentPackageJsonPath, projectPackageJsonPath, isBootstrapped);
|
|
93
|
-
if (decision.shouldHandoff && projectPackageJsonPath) {
|
|
94
|
-
runProjectLocalServer(projectPackageJsonPath);
|
|
95
|
-
}
|
|
96
|
-
process.env.LETSRUNIT_MCP_RUNTIME_MODE = decision.runtimeMode;
|
|
97
|
-
return decision.runtimeMode;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// src/index.ts
|
|
101
|
-
var { version } = createRequire(import.meta.url)("../package.json");
|
|
8
|
+
var version = "0.14.4" ;
|
|
102
9
|
bootstrapProjectServer();
|
|
103
10
|
var { SessionManager } = await import('./sessions-BYH3NJQG.js');
|
|
104
11
|
var {
|
|
@@ -112,7 +19,7 @@ var {
|
|
|
112
19
|
registerSessionClose,
|
|
113
20
|
registerSessionStart,
|
|
114
21
|
registerSnapshot
|
|
115
|
-
} = await import('./tools-
|
|
22
|
+
} = await import('./tools-CNEENZR2.js');
|
|
116
23
|
var sessions = new SessionManager();
|
|
117
24
|
var server = new McpServer({
|
|
118
25
|
name: "letsrunit",
|
|
@@ -129,7 +36,7 @@ registerListSteps(server, sessions);
|
|
|
129
36
|
registerListSessions(server, sessions);
|
|
130
37
|
registerDiff(server, sessions);
|
|
131
38
|
if (process.env.LETSRUNIT_MCP_DIAGNOSTICS === "enabled") {
|
|
132
|
-
registerDiagnostics(server);
|
|
39
|
+
registerDiagnostics(server, sessions);
|
|
133
40
|
}
|
|
134
41
|
var transport = new StdioServerTransport();
|
|
135
42
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;AAMA,IAAM,OAAA,GAAsD,QAAA,CAAwB;AACpF,sBAAA,EAAuB;AAEvB,IAAM,EAAE,cAAA,EAAe,GAAI,MAAM,OAAO,wBAAY,CAAA;AACpD,IAAM;AAAA,EACJ,aAAA;AAAA,EACA,mBAAA;AAAA,EACA,YAAA;AAAA,EACA,iBAAA;AAAA,EACA,oBAAA;AAAA,EACA,WAAA;AAAA,EACA,kBAAA;AAAA,EACA,oBAAA;AAAA,EACA,oBAAA;AAAA,EACA;AACF,CAAA,GAAI,MAAM,OAAO,qBAAS,CAAA;AAE1B,IAAM,QAAA,GAAW,IAAI,cAAA,EAAe;AAEpC,IAAM,MAAA,GAAS,IAAI,SAAA,CAAU;AAAA,EAC3B,IAAA,EAAM,WAAA;AAAA,EACN,OAAA;AAAA,EACA,UAAA,EAAY;AACd,CAAC,CAAA;AAED,oBAAA,CAAqB,QAAQ,QAAQ,CAAA;AACrC,WAAA,CAAY,QAAQ,QAAQ,CAAA;AAC5B,gBAAA,CAAiB,QAAQ,QAAQ,CAAA;AACjC,kBAAA,CAAmB,QAAQ,QAAQ,CAAA;AACnC,aAAA,CAAc,QAAQ,QAAQ,CAAA;AAC9B,oBAAA,CAAqB,QAAQ,QAAQ,CAAA;AACrC,iBAAA,CAAkB,QAAQ,QAAQ,CAAA;AAClC,oBAAA,CAAqB,QAAQ,QAAQ,CAAA;AACrC,YAAA,CAAa,QAAQ,QAAQ,CAAA;AAC7B,IAAI,OAAA,CAAQ,GAAA,CAAI,yBAAA,KAA8B,SAAA,EAAW;AACvD,EAAA,mBAAA,CAAoB,QAAQ,QAAQ,CAAA;AACtC;AAEA,IAAM,SAAA,GAAY,IAAI,oBAAA,EAAqB;AAC3C,MAAM,MAAA,CAAO,QAAQ,SAAS,CAAA","file":"index.js","sourcesContent":["import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { bootstrapProjectServer } from './bootstrap';\n\ndeclare const __LETSRUNIT_VERSION__: string;\n\nconst version = typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';\nbootstrapProjectServer();\n\nconst { SessionManager } = await import('./sessions');\nconst {\n registerDebug,\n registerDiagnostics,\n registerDiff,\n registerListSteps,\n registerListSessions,\n registerRun,\n registerScreenshot,\n registerSessionClose,\n registerSessionStart,\n registerSnapshot,\n} = await import('./tools');\n\nconst sessions = new SessionManager();\n\nconst server = new McpServer({\n name: 'letsrunit',\n version,\n websiteUrl: 'https://letsrunit.ai',\n});\n\nregisterSessionStart(server, sessions);\nregisterRun(server, sessions);\nregisterSnapshot(server, sessions);\nregisterScreenshot(server, sessions);\nregisterDebug(server, sessions);\nregisterSessionClose(server, sessions);\nregisterListSteps(server, sessions);\nregisterListSessions(server, sessions);\nregisterDiff(server, sessions);\nif (process.env.LETSRUNIT_MCP_DIAGNOSTICS === 'enabled') {\n registerDiagnostics(server, sessions);\n}\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"]}
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as __m from 'node:module';
|
|
3
|
+
import { decideHandoff } from './chunk-XJCBPTOU.js';
|
|
3
4
|
import { z } from 'zod';
|
|
4
|
-
import { createRequire } from 'module';
|
|
5
5
|
import { loadConfiguration } from '@cucumber/cucumber/api';
|
|
6
6
|
import { registry } from '@letsrunit/bdd';
|
|
7
7
|
import { readFileSync, existsSync, realpathSync } from 'fs';
|
|
8
8
|
import { mkdir, writeFile, glob } from 'fs/promises';
|
|
9
|
+
import { createRequire } from 'module';
|
|
9
10
|
import { join, dirname, resolve, isAbsolute } from 'path';
|
|
10
|
-
import { pathToFileURL } from 'url';
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from 'url';
|
|
11
12
|
import { unifiedHtmlDiff, screenshotElement, screenshot, scrubHtml } from '@letsrunit/playwright';
|
|
12
13
|
import { openStore, findLastTest, findArtifacts } from '@letsrunit/store';
|
|
13
14
|
import { execSync } from 'child_process';
|
|
@@ -76,7 +77,7 @@ async function expandPathPatterns(baseDir, patterns) {
|
|
|
76
77
|
const files = /* @__PURE__ */ new Set();
|
|
77
78
|
for (const pattern of patterns) {
|
|
78
79
|
if (hasGlobMagic(pattern)) {
|
|
79
|
-
for await (const match of glob(pattern, { cwd: baseDir
|
|
80
|
+
for await (const match of glob(pattern, { cwd: baseDir })) {
|
|
80
81
|
files.add(normalizeMatch(baseDir, match));
|
|
81
82
|
}
|
|
82
83
|
continue;
|
|
@@ -89,7 +90,7 @@ async function resolveSupportEntries(baseDir, entries) {
|
|
|
89
90
|
const resolved = [];
|
|
90
91
|
for (const entry of entries) {
|
|
91
92
|
if (hasGlobMagic(entry)) {
|
|
92
|
-
for await (const match of glob(entry, { cwd: baseDir
|
|
93
|
+
for await (const match of glob(entry, { cwd: baseDir })) {
|
|
93
94
|
resolved.push({ kind: "path", value: normalizeMatch(baseDir, match) });
|
|
94
95
|
}
|
|
95
96
|
continue;
|
|
@@ -135,6 +136,14 @@ function toRealpath(path) {
|
|
|
135
136
|
return path;
|
|
136
137
|
}
|
|
137
138
|
}
|
|
139
|
+
function pickLetsrunitEnv() {
|
|
140
|
+
return Object.fromEntries(
|
|
141
|
+
Object.entries(process.env).filter(([key, value]) => key.startsWith("LETSRUNIT_") && typeof value === "string").map(([key, value]) => [key, value])
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
function resolveMcpServerVersion() {
|
|
145
|
+
return "0.14.4" ;
|
|
146
|
+
}
|
|
138
147
|
async function collectSupportDiagnostics(cwd) {
|
|
139
148
|
const effectiveCwd = resolveEffectiveCwd(cwd);
|
|
140
149
|
const projectRoot = resolve(effectiveCwd);
|
|
@@ -146,9 +155,18 @@ async function collectSupportDiagnostics(cwd) {
|
|
|
146
155
|
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
147
156
|
const serverBddPath = toRealpath(resolveFrom("@letsrunit/bdd", import.meta.url));
|
|
148
157
|
const projectBddPath = toRealpath(resolveFrom("@letsrunit/bdd", resolve(projectRoot, "package.json")));
|
|
158
|
+
const projectMcpEntryPath = resolveFrom("@letsrunit/mcp-server", resolve(projectRoot, "package.json"));
|
|
159
|
+
const currentEntrypointPath = toRealpath(fileURLToPath(import.meta.url));
|
|
160
|
+
const projectEntrypointPath = toRealpath(projectMcpEntryPath);
|
|
161
|
+
const handoffDecision = decideHandoff(
|
|
162
|
+
currentEntrypointPath,
|
|
163
|
+
projectEntrypointPath,
|
|
164
|
+
process.env.LETSRUNIT_MCP_BOOTSTRAPPED === "1"
|
|
165
|
+
);
|
|
149
166
|
const serverMcpPath = toRealpath(resolveFrom("@letsrunit/mcp-server", import.meta.url));
|
|
150
|
-
const projectMcpPath = toRealpath(
|
|
151
|
-
const
|
|
167
|
+
const projectMcpPath = toRealpath(projectMcpEntryPath);
|
|
168
|
+
const executablePath = toRealpath(process.argv[1] ?? null);
|
|
169
|
+
const version = resolveMcpServerVersion();
|
|
152
170
|
const registryDefinitions = registry.defs.map((def) => ({
|
|
153
171
|
type: def.type,
|
|
154
172
|
source: def.source,
|
|
@@ -168,16 +186,20 @@ async function collectSupportDiagnostics(cwd) {
|
|
|
168
186
|
loadedProjectRoots: [...loadedProjectRoots].sort(),
|
|
169
187
|
loadedSupportEntries: [...loadedSupportEntries].sort(),
|
|
170
188
|
mcpServer: {
|
|
171
|
-
|
|
172
|
-
|
|
189
|
+
version,
|
|
190
|
+
executablePath,
|
|
191
|
+
projectServerUsed: handoffDecision.runtimeMode === "project",
|
|
192
|
+
handoffDecision: {
|
|
193
|
+
shouldHandoff: handoffDecision.shouldHandoff,
|
|
194
|
+
runtimeMode: handoffDecision.runtimeMode
|
|
195
|
+
},
|
|
173
196
|
serverMcpPath,
|
|
174
|
-
projectMcpPath
|
|
175
|
-
sameModule: !!serverMcpPath && !!projectMcpPath && serverMcpPath === projectMcpPath
|
|
197
|
+
projectMcpPath
|
|
176
198
|
},
|
|
199
|
+
letsrunitEnv: pickLetsrunitEnv(),
|
|
177
200
|
moduleResolution: {
|
|
178
201
|
serverBddPath,
|
|
179
|
-
projectBddPath
|
|
180
|
-
sameModule: !!serverBddPath && !!projectBddPath && serverBddPath === projectBddPath
|
|
202
|
+
projectBddPath
|
|
181
203
|
},
|
|
182
204
|
registry: {
|
|
183
205
|
total: registryDefinitions.length,
|
|
@@ -219,17 +241,28 @@ async function loadSupportFiles(cwd) {
|
|
|
219
241
|
}
|
|
220
242
|
|
|
221
243
|
// src/tools/diagnostics.ts
|
|
222
|
-
function registerDiagnostics(server) {
|
|
244
|
+
function registerDiagnostics(server, sessions) {
|
|
223
245
|
server.registerTool(
|
|
224
246
|
"letsrunit_diagnostics",
|
|
225
247
|
{
|
|
226
248
|
description: "Return runtime diagnostics for MCP support-file loading (cwd resolution, cucumber config path, support entries). Available only when LETSRUNIT_MCP_DIAGNOSTICS=enabled.",
|
|
227
|
-
inputSchema: {
|
|
249
|
+
inputSchema: {
|
|
250
|
+
sessionId: z.string().describe("Session ID returned by letsrunit_session_start")
|
|
251
|
+
}
|
|
228
252
|
},
|
|
229
|
-
async () => {
|
|
253
|
+
async (input) => {
|
|
230
254
|
try {
|
|
231
255
|
const diagnostics = await collectSupportDiagnostics();
|
|
232
|
-
|
|
256
|
+
const session = sessions.get(input.sessionId);
|
|
257
|
+
const sessionInfo = {
|
|
258
|
+
sessionId: session.id,
|
|
259
|
+
createdAt: session.createdAt,
|
|
260
|
+
lastActivity: session.lastActivity,
|
|
261
|
+
stepCount: session.stepCount,
|
|
262
|
+
artifactDir: session.artifactDir,
|
|
263
|
+
pageUrl: session.controller.page.url()
|
|
264
|
+
};
|
|
265
|
+
return text(JSON.stringify({ ...diagnostics, session: sessionInfo }));
|
|
233
266
|
} catch (e) {
|
|
234
267
|
return err(`Diagnostics failed: ${e.message}`);
|
|
235
268
|
}
|
|
@@ -532,5 +565,5 @@ function registerSnapshot(server, sessions) {
|
|
|
532
565
|
}
|
|
533
566
|
|
|
534
567
|
export { registerDebug, registerDiagnostics, registerDiff, registerListSessions, registerListSteps, registerRun, registerScreenshot, registerSessionClose, registerSessionStart, registerSnapshot };
|
|
535
|
-
//# sourceMappingURL=tools-
|
|
536
|
-
//# sourceMappingURL=tools-
|
|
568
|
+
//# sourceMappingURL=tools-CNEENZR2.js.map
|
|
569
|
+
//# sourceMappingURL=tools-CNEENZR2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utility/response.ts","../src/tools/debug.ts","../src/utility/support.ts","../src/tools/diagnostics.ts","../src/tools/diff.ts","../src/tools/list-steps.ts","../src/tools/list-sessions.ts","../src/utility/gherkin.ts","../src/tools/run.ts","../src/tools/screenshot.ts","../src/tools/session-close.ts","../src/tools/session-start.ts","../src/tools/snapshot.ts"],"names":["z","join"],"mappings":"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","file":"tools-CNEENZR2.js","sourcesContent":["export function text(content: string) {\n return { content: [{ type: 'text' as const, text: content }] };\n}\n\nexport function err(message: string) {\n return { content: [{ type: 'text' as const, text: message }], isError: true };\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { text } from '../utility/response';\n\nexport function registerDebug(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_debug',\n {\n description:\n 'Evaluate JavaScript on the current page via Playwright page.evaluate(). Use for debugging — not for test logic.',\n inputSchema: {\n sessionId: z.string().describe('Session ID'),\n script: z.string().describe('JavaScript expression or function body to evaluate in the page context'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const result = await session.controller.page.evaluate(input.script);\n return text(JSON.stringify({ result }));\n } catch (e) {\n return text(JSON.stringify({ result: null, error: (e as Error).message }));\n }\n },\n );\n}\n","import { loadConfiguration } from '@cucumber/cucumber/api';\nimport { registry } from '@letsrunit/bdd';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { glob } from 'node:fs/promises';\nimport { createRequire } from 'node:module';\nimport { isAbsolute, resolve } from 'node:path';\nimport { fileURLToPath, pathToFileURL } from 'node:url';\nimport { decideHandoff } from '../bootstrap';\n\ndeclare const __LETSRUNIT_VERSION__: string;\n\ntype CucumberConfig = {\n require?: unknown;\n import?: unknown;\n letsrunit?: {\n ignore?: unknown;\n };\n};\n\ntype SupportEntry = { kind: 'path'; value: string } | { kind: 'module'; value: string };\n\nconst CUCUMBER_CONFIG_FILES = [\n 'cucumber.js',\n 'cucumber.mjs',\n 'cucumber.cjs',\n 'cucumber.ts',\n 'cucumber.mts',\n 'cucumber.cts',\n];\n\nconst loadedProjectRoots = new Set<string>();\nconst loadedSupportEntries = new Set<string>();\n\nexport type SupportDiagnostics = {\n envProjectCwd: string | null;\n processCwd: string;\n inputCwd: string | null;\n effectiveCwd: string;\n projectRoot: string;\n cucumberConfigPath: string | null;\n supportPatterns: string[];\n ignorePatterns: string[];\n ignoredPaths: string[];\n supportEntries: SupportEntry[];\n loadedProjectRoots: string[];\n loadedSupportEntries: string[];\n mcpServer: {\n version: string;\n executablePath: string | null;\n projectServerUsed: boolean;\n handoffDecision: {\n shouldHandoff: boolean;\n runtimeMode: string;\n };\n serverMcpPath: string | null;\n projectMcpPath: string | null;\n };\n letsrunitEnv: Record<string, string>;\n moduleResolution: {\n serverBddPath: string | null;\n projectBddPath: string | null;\n };\n registry: {\n total: number;\n byType: {\n Given: number;\n When: number;\n Then: number;\n };\n definitions: Array<{\n type: 'Given' | 'When' | 'Then';\n source: string;\n comment?: string;\n }>;\n };\n};\n\nfunction toStrings(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((entry): entry is string => typeof entry === 'string');\n}\n\nfunction hasGlobMagic(input: string): boolean {\n return /[*?[\\]{}]/.test(input);\n}\n\nfunction isPathLike(input: string): boolean {\n return input.startsWith('.') || input.startsWith('/') || /^[A-Za-z]:[\\\\/]/.test(input);\n}\n\nfunction toAbsolutePath(baseDir: string, input: string): string {\n return isAbsolute(input) ? resolve(input) : resolve(baseDir, input);\n}\n\nfunction normalizeMatch(baseDir: string, match: string): string {\n return isAbsolute(match) ? resolve(match) : resolve(baseDir, match);\n}\n\nasync function expandPathPatterns(baseDir: string, patterns: string[]): Promise<Set<string>> {\n const files = new Set<string>();\n\n for (const pattern of patterns) {\n if (hasGlobMagic(pattern)) {\n for await (const match of glob(pattern, { cwd: baseDir })) {\n files.add(normalizeMatch(baseDir, match));\n }\n continue;\n }\n\n files.add(toAbsolutePath(baseDir, pattern));\n }\n\n return files;\n}\n\nasync function resolveSupportEntries(baseDir: string, entries: string[]): Promise<SupportEntry[]> {\n const resolved: SupportEntry[] = [];\n\n for (const entry of entries) {\n if (hasGlobMagic(entry)) {\n for await (const match of glob(entry, { cwd: baseDir })) {\n resolved.push({ kind: 'path', value: normalizeMatch(baseDir, match) });\n }\n continue;\n }\n\n if (!isPathLike(entry)) {\n resolved.push({ kind: 'module', value: entry });\n continue;\n }\n\n resolved.push({ kind: 'path', value: toAbsolutePath(baseDir, entry) });\n }\n\n return resolved;\n}\n\nfunction findCucumberConfig(cwd: string): string | null {\n for (const filename of CUCUMBER_CONFIG_FILES) {\n const path = resolve(cwd, filename);\n if (existsSync(path)) return path;\n }\n\n return null;\n}\n\nasync function loadLetsrunitIgnorePatterns(cwd: string): Promise<string[]> {\n const configPath = findCucumberConfig(cwd);\n if (!configPath) return [];\n\n const configModule = await import(pathToFileURL(configPath).href);\n const config = (configModule.default ?? configModule) as CucumberConfig;\n\n return toStrings(config.letsrunit?.ignore);\n}\n\nfunction resolveEffectiveCwd(cwd?: string): string {\n return cwd ?? process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd();\n}\n\nfunction resolveFrom(moduleId: string, fromPath: string): string | null {\n try {\n const req = createRequire(fromPath);\n return req.resolve(moduleId);\n } catch {\n return null;\n }\n}\n\nfunction toRealpath(path: string | null): string | null {\n if (!path) return null;\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\nfunction pickLetsrunitEnv(): Record<string, string> {\n return Object.fromEntries(\n Object.entries(process.env)\n .filter(([key, value]) => key.startsWith('LETSRUNIT_') && typeof value === 'string')\n .map(([key, value]) => [key, value as string]),\n );\n}\n\nfunction resolveMcpServerVersion(): string {\n return typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';\n}\n\nexport async function collectSupportDiagnostics(cwd?: string): Promise<SupportDiagnostics> {\n const effectiveCwd = resolveEffectiveCwd(cwd);\n const projectRoot = resolve(effectiveCwd);\n const cucumberConfigPath = findCucumberConfig(projectRoot);\n const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });\n const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];\n const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);\n const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);\n const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);\n const serverBddPath = toRealpath(resolveFrom('@letsrunit/bdd', import.meta.url));\n const projectBddPath = toRealpath(resolveFrom('@letsrunit/bdd', resolve(projectRoot, 'package.json')));\n const projectMcpEntryPath = resolveFrom('@letsrunit/mcp-server', resolve(projectRoot, 'package.json'));\n const currentEntrypointPath = toRealpath(fileURLToPath(import.meta.url));\n const projectEntrypointPath = toRealpath(projectMcpEntryPath);\n const handoffDecision = decideHandoff(\n currentEntrypointPath,\n projectEntrypointPath,\n process.env.LETSRUNIT_MCP_BOOTSTRAPPED === '1',\n );\n const serverMcpPath = toRealpath(resolveFrom('@letsrunit/mcp-server', import.meta.url));\n const projectMcpPath = toRealpath(projectMcpEntryPath);\n const executablePath = toRealpath(process.argv[1] ?? null);\n const version = resolveMcpServerVersion();\n const registryDefinitions = registry.defs.map((def) => ({\n type: def.type,\n source: def.source,\n comment: def.comment,\n }));\n\n return {\n envProjectCwd: process.env.LETSRUNIT_PROJECT_CWD ?? null,\n processCwd: process.cwd(),\n inputCwd: cwd ?? null,\n effectiveCwd,\n projectRoot,\n cucumberConfigPath,\n supportPatterns,\n ignorePatterns,\n ignoredPaths: [...ignoredPaths].sort(),\n supportEntries,\n loadedProjectRoots: [...loadedProjectRoots].sort(),\n loadedSupportEntries: [...loadedSupportEntries].sort(),\n mcpServer: {\n version,\n executablePath,\n projectServerUsed: handoffDecision.runtimeMode === 'project',\n handoffDecision: {\n shouldHandoff: handoffDecision.shouldHandoff,\n runtimeMode: handoffDecision.runtimeMode,\n },\n serverMcpPath,\n projectMcpPath,\n },\n letsrunitEnv: pickLetsrunitEnv(),\n moduleResolution: {\n serverBddPath,\n projectBddPath,\n },\n registry: {\n total: registryDefinitions.length,\n byType: {\n Given: registryDefinitions.filter((d) => d.type === 'Given').length,\n When: registryDefinitions.filter((d) => d.type === 'When').length,\n Then: registryDefinitions.filter((d) => d.type === 'Then').length,\n },\n definitions: registryDefinitions,\n },\n };\n}\n\nexport async function loadSupportFiles(cwd?: string): Promise<void> {\n const projectRoot = resolve(resolveEffectiveCwd(cwd));\n if (loadedProjectRoots.has(projectRoot)) return;\n\n const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });\n const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];\n if (supportPatterns.length === 0) {\n loadedProjectRoots.add(projectRoot);\n return;\n }\n\n const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);\n const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);\n const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);\n\n for (const entry of supportEntries) {\n if (entry.kind === 'path' && ignoredPaths.has(entry.value)) {\n continue;\n }\n\n const key = `${entry.kind}:${entry.value}`;\n if (loadedSupportEntries.has(key)) continue;\n\n if (entry.kind === 'path') {\n await import(pathToFileURL(entry.value).href);\n } else {\n await import(entry.value);\n }\n\n loadedSupportEntries.add(key);\n }\n\n loadedProjectRoots.add(projectRoot);\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { collectSupportDiagnostics } from '../utility/support';\nimport { err, text } from '../utility/response';\n\nexport function registerDiagnostics(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_diagnostics',\n {\n description:\n 'Return runtime diagnostics for MCP support-file loading (cwd resolution, cucumber config path, support entries). Available only when LETSRUNIT_MCP_DIAGNOSTICS=enabled.',\n inputSchema: {\n sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),\n },\n },\n async (input) => {\n try {\n const diagnostics = await collectSupportDiagnostics();\n const session = sessions.get(input.sessionId);\n const sessionInfo = {\n sessionId: session.id,\n createdAt: session.createdAt,\n lastActivity: session.lastActivity,\n stepCount: session.stepCount,\n artifactDir: session.artifactDir,\n pageUrl: session.controller.page.url(),\n };\n return text(JSON.stringify({ ...diagnostics, session: sessionInfo }));\n } catch (e) {\n return err(`Diagnostics failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { unifiedHtmlDiff } from '@letsrunit/playwright';\nimport { openStore, findLastTest, findArtifacts } from '@letsrunit/store';\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nconst DEFAULT_DB_PATH = join(process.cwd(), '.letsrunit', 'letsrunit.db');\n\nfunction getDbPath(): string {\n return process.env.LETSRUNIT_DB_PATH ?? DEFAULT_DB_PATH;\n}\n\nfunction resolveAllowedCommits(): string[] | undefined {\n try {\n const output = execSync('git log --format=%H', { encoding: 'utf8' });\n return output.trim().split('\\n').filter(Boolean);\n } catch {\n return undefined;\n }\n}\n\nexport function registerDiff(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_diff',\n {\n description:\n 'Diff the current live page against the HTML snapshot from the last passing test of a scenario. ' +\n 'Pass the scenarioId returned by letsrunit_run. ' +\n 'Returns a unified HTML diff and paths to baseline screenshots. ' +\n 'By default only considers baseline tests from the current git ancestry (gitTreeOnly: true).',\n inputSchema: {\n sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),\n scenarioId: z.string().describe('Scenario UUID returned by letsrunit_run'),\n gitTreeOnly: z\n .boolean()\n .optional()\n .describe('Restrict baseline to tests from the current git ancestry (default: true)'),\n },\n },\n async (input) => {\n const dbPath = getDbPath();\n const artifactDir = join(dirname(dbPath), 'artifacts');\n let db: ReturnType<typeof openStore> | undefined;\n\n try {\n try {\n db = openStore(dbPath);\n } catch {\n return err('Could not open the letsrunit store. Run cucumber with the store formatter first.');\n }\n\n const allowedCommits = (input.gitTreeOnly ?? true) ? resolveAllowedCommits() : undefined;\n\n const test = findLastTest(db, input.scenarioId, 'passed', allowedCommits ?? undefined);\n if (!test) {\n return err(\n allowedCommits\n ? 'No passing test found for this scenario in the current git ancestry. Try gitTreeOnly: false or run cucumber first.'\n : 'No passing test found for this scenario.',\n );\n }\n\n const artifacts = findArtifacts(db, test.id);\n\n const htmlArtifact = [...artifacts].reverse().find((a) => a.filename.endsWith('.html'));\n if (!htmlArtifact) {\n return err('No HTML snapshot found in the baseline test. Ensure the store formatter is configured.');\n }\n\n const storedHtml = readFileSync(join(artifactDir, htmlArtifact.filename), 'utf-8');\n\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const diff = await unifiedHtmlDiff({ html: storedHtml, url: 'about:blank' }, session.controller.page);\n\n const screenshots = artifacts\n .filter((a) => a.stepIdx === htmlArtifact.stepIdx && a.filename.endsWith('.png'))\n .map((a) => join(artifactDir, a.filename));\n\n return text(\n JSON.stringify({\n diff,\n baseline: {\n testId: test.id,\n commit: test.gitCommit,\n screenshots,\n },\n }),\n );\n } catch (e) {\n return err(`Diff failed: ${(e as Error).message}`);\n } finally {\n db?.close();\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nconst stepTypeSchema = z.enum(['Given', 'When', 'Then']);\n\nexport function registerListSteps(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_list_steps',\n {\n description:\n 'List available step definitions for a session. Optionally filter by step type (Given/When/Then).',\n inputSchema: {\n sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),\n type: stepTypeSchema.optional().describe('Optional step type filter'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const steps = session.controller.listSteps(input.type);\n return text(JSON.stringify({ steps }));\n } catch (e) {\n return err(`List steps failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { type SessionManager } from '../sessions';\nimport { text } from '../utility/response';\n\nexport function registerListSessions(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_list_sessions',\n {\n description: 'List all active browser sessions.',\n inputSchema: {},\n },\n async () => {\n const list = sessions.list().map((s) => ({\n sessionId: s.id,\n createdAt: s.createdAt,\n lastActivity: s.lastActivity,\n stepCount: s.stepCount,\n artifactDir: s.artifactDir,\n }));\n\n return text(JSON.stringify({ sessions: list }));\n },\n );\n}\n","export function normalizeGherkin(input: string): string {\n const trimmed = input.trim();\n\n if (/^(Feature|Scenario|Background):/im.test(trimmed)) {\n return trimmed;\n }\n\n return `Feature: MCP\\n\\nScenario: Steps\\n ${trimmed.split('\\n').join('\\n ')}`;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { scenarioIdFromGherkin } from '@letsrunit/gherkin';\nimport type { SessionManager } from '../sessions';\nimport { normalizeGherkin } from '../utility/gherkin';\nimport { err, text } from '../utility/response';\n\nexport function registerRun(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_run',\n {\n description:\n 'Execute Gherkin steps or a complete feature in the browser. ' +\n 'Accepts a single step line, multiple step lines, a full Scenario, or a full Feature. ' +\n 'Returns status, steps, reason on failure, and journal entries. Does not return a page snapshot — call letsrunit_snapshot explicitly if you need the DOM.',\n inputSchema: {\n sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),\n input: z\n .string()\n .describe(\n 'Gherkin text to execute: one or more step lines (e.g. \"Given I am on \\\\\"https://example.com\\\\\"\"), a Scenario block, or a full Feature block.',\n ),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const feature = normalizeGherkin(input.input);\n\n session.sink.clear();\n const result = await session.controller.run(feature);\n session.stepCount += result.steps.length;\n\n return text(\n JSON.stringify({\n status: result.status,\n steps: result.steps,\n reason: result.reason?.message,\n journal: session.sink.getEntries(),\n scenarioId: scenarioIdFromGherkin(input.input),\n }),\n );\n } catch (e) {\n return err(`Run failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { screenshot, screenshotElement } from '@letsrunit/playwright';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nexport function registerScreenshot(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_screenshot',\n {\n description:\n 'Take a screenshot of the current page. Optionally crop to a specific element (selector) or highlight elements before capturing (mask).',\n inputSchema: {\n sessionId: z.string().describe('Session ID'),\n selector: z\n .string()\n .optional()\n .describe('CSS selector — crop screenshot to the bounding box of this element'),\n mask: z\n .array(z.string())\n .optional()\n .describe('CSS selectors whose matching elements are highlighted (dark overlay, element spotlighted).'),\n fullPage: z.boolean().optional().describe('Capture the full scrollable page (default: false)'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const page = session.controller.page;\n\n let file: File;\n\n if (input.selector) {\n file = await screenshotElement(page, input.selector);\n } else {\n const masks = input.mask?.map((sel) => page.locator(sel)) ?? [];\n file = await screenshot(page, {\n fullPage: input.fullPage ?? false,\n ...(masks.length ? { mask: masks } : {}),\n });\n }\n\n await mkdir(session.artifactDir, { recursive: true });\n const path = join(session.artifactDir, file.name);\n await writeFile(path, await file.bytes());\n\n return text(JSON.stringify({ path, mimeType: 'image/png' }));\n } catch (e) {\n return err(`Screenshot failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nexport function registerSessionClose(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_session_close',\n {\n description: 'Close a browser session and release its resources.',\n inputSchema: {\n sessionId: z.string().describe('Session ID to close'),\n },\n },\n async (input) => {\n try {\n await sessions.close(input.sessionId);\n return text(JSON.stringify({ closed: true }));\n } catch (e) {\n return err(`Failed to close session: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\nimport { loadSupportFiles } from '../utility/support';\n\nexport function registerSessionStart(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_session_start',\n {\n description:\n 'Launch a new browser session. Does not navigate anywhere — use letsrunit_run with a Given step to navigate. Set baseURL to enable relative paths like \"Given I\\'m on the homepage\".',\n inputSchema: {\n baseURL: z.string().optional().describe('Base URL for the session, e.g. \"http://localhost:3000\". Enables relative paths in Given steps like \"Given I\\'m on the homepage\" or \"Given I\\'m on page \\\\\"/login\\\\\"\"'),\n language: z.string().optional().describe(\"Browser language code, e.g. 'en', 'fr'\"),\n headless: z.boolean().optional().describe('Run browser in headless mode (default: true)'),\n viewportWidth: z.number().int().optional().describe('Viewport width in pixels (default: 1280)'),\n viewportHeight: z.number().int().optional().describe('Viewport height in pixels (default: 720)'),\n },\n },\n async (input) => {\n try {\n if (process.env.LETSRUNIT_MCP_RUNTIME_MODE === 'project') {\n await loadSupportFiles();\n }\n\n const viewport =\n input.viewportWidth || input.viewportHeight\n ? { width: input.viewportWidth ?? 1280, height: input.viewportHeight ?? 720 }\n : undefined;\n\n const session = await sessions.create({\n baseURL: input.baseURL,\n headless: input.headless ?? true,\n locale: input.language,\n viewport,\n });\n\n return text(JSON.stringify({ sessionId: session.id }));\n } catch (e) {\n return err(`Failed to start session: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { scrubHtml } from '@letsrunit/playwright';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nconst stripAttributesSchema = z.nativeEnum({\n none: 0,\n semantic: 1,\n aggressive: 2,\n});\n\nexport function registerSnapshot(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_snapshot',\n {\n description:\n 'Get the current page HTML, scrubbed for LLM consumption. Use selector to scope to a DOM subtree.',\n inputSchema: {\n sessionId: z.string().describe('Session ID'),\n selector: z\n .string()\n .optional()\n .describe(\"CSS selector — return only the matching element's outer HTML instead of the full page\"),\n dropHidden: z.boolean().optional().describe('Remove hidden/inert nodes (default: true)'),\n dropHead: z.boolean().optional().describe('Remove the <head> element (default: true)'),\n pickMain: z.boolean().optional().describe('Keep only the <main> element (default: auto)'),\n stripAttributes: stripAttributesSchema\n .optional()\n .describe('Attribute allowlist level: 0=none, 1=semantic (default), 2=aggressive'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const page = session.controller.page;\n const url = page.url();\n\n const opts = {\n dropHidden: input.dropHidden,\n dropHead: input.dropHead,\n pickMain: input.pickMain,\n stripAttributes: input.stripAttributes,\n };\n\n let html: string;\n\n if (input.selector) {\n const rawHtml = await page\n .locator(input.selector)\n .first()\n .evaluate((el: Element) => el.outerHTML);\n html = await scrubHtml({ html: rawHtml, url }, opts);\n } else {\n html = await scrubHtml(page, opts);\n }\n\n return text(JSON.stringify({ url, html }));\n } catch (e) {\n return err(`Snapshot failed: ${(e as Error).message}`);\n }\n },\n );\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@letsrunit/mcp-server",
|
|
3
|
-
"version": "0.14.
|
|
3
|
+
"version": "0.14.4",
|
|
4
4
|
"description": "MCP server for letsrunit — AI-agent browser test generation and execution",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"testing",
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"packageManager": "yarn@4.10.3",
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"@cucumber/cucumber": "^12.7.0",
|
|
50
|
-
"@letsrunit/bdd": "0.14.
|
|
51
|
-
"@letsrunit/controller": "0.14.
|
|
52
|
-
"@letsrunit/gherkin": "0.14.
|
|
53
|
-
"@letsrunit/journal": "0.14.
|
|
54
|
-
"@letsrunit/playwright": "0.14.
|
|
55
|
-
"@letsrunit/store": "0.14.
|
|
56
|
-
"@letsrunit/utils": "0.14.
|
|
50
|
+
"@letsrunit/bdd": "0.14.4",
|
|
51
|
+
"@letsrunit/controller": "0.14.4",
|
|
52
|
+
"@letsrunit/gherkin": "0.14.4",
|
|
53
|
+
"@letsrunit/journal": "0.14.4",
|
|
54
|
+
"@letsrunit/playwright": "0.14.4",
|
|
55
|
+
"@letsrunit/store": "0.14.4",
|
|
56
|
+
"@letsrunit/utils": "0.14.4",
|
|
57
57
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
58
58
|
"@playwright/test": "^1.57.0",
|
|
59
59
|
"zod": "^4.3.5"
|
package/src/bootstrap.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import {
|
|
2
|
+
import { realpathSync } from 'node:fs';
|
|
3
3
|
import { createRequire } from 'node:module';
|
|
4
|
-
import {
|
|
4
|
+
import { resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
5
6
|
|
|
6
7
|
export type McpRuntimeMode = 'project' | 'standalone';
|
|
7
8
|
|
|
@@ -10,10 +11,6 @@ export type HandoffDecision = {
|
|
|
10
11
|
runtimeMode: McpRuntimeMode;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
|
-
type PackageJsonLike = {
|
|
14
|
-
bin?: string | Record<string, string>;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
14
|
function resolveProjectRoot(): string {
|
|
18
15
|
return resolve(process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd());
|
|
19
16
|
}
|
|
@@ -27,19 +24,6 @@ function resolveFromProject(moduleId: string, projectRoot: string): string | nul
|
|
|
27
24
|
}
|
|
28
25
|
}
|
|
29
26
|
|
|
30
|
-
function findPackageJsonPath(entryPath: string | null): string | null {
|
|
31
|
-
if (!entryPath) return null;
|
|
32
|
-
|
|
33
|
-
let dir = dirname(entryPath);
|
|
34
|
-
while (dir !== dirname(dir)) {
|
|
35
|
-
const packageJsonPath = resolve(dir, 'package.json');
|
|
36
|
-
if (existsSync(packageJsonPath)) return packageJsonPath;
|
|
37
|
-
dir = dirname(dir);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return null;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
27
|
function toRealpath(path: string | null): string | null {
|
|
44
28
|
if (!path) return null;
|
|
45
29
|
try {
|
|
@@ -49,41 +33,21 @@ function toRealpath(path: string | null): string | null {
|
|
|
49
33
|
}
|
|
50
34
|
}
|
|
51
35
|
|
|
52
|
-
function
|
|
36
|
+
function sameEntrypoint(a: string | null, b: string | null): boolean {
|
|
53
37
|
if (!a || !b) return false;
|
|
54
|
-
return
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function resolveBinPath(packageJsonPath: string): string {
|
|
58
|
-
const text = readFileSync(packageJsonPath, 'utf8');
|
|
59
|
-
const pkg = JSON.parse(text) as PackageJsonLike;
|
|
60
|
-
|
|
61
|
-
if (typeof pkg.bin === 'string') {
|
|
62
|
-
return resolve(dirname(packageJsonPath), pkg.bin);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
if (pkg.bin && typeof pkg.bin === 'object') {
|
|
66
|
-
const named = pkg.bin['letsrunit-mcp'];
|
|
67
|
-
const first = Object.values(pkg.bin)[0];
|
|
68
|
-
const bin = named ?? first;
|
|
69
|
-
if (typeof bin === 'string') {
|
|
70
|
-
return resolve(dirname(packageJsonPath), bin);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
throw new Error(`Unable to resolve mcp bin from ${packageJsonPath}`);
|
|
38
|
+
return a === b;
|
|
75
39
|
}
|
|
76
40
|
|
|
77
41
|
export function decideHandoff(
|
|
78
|
-
|
|
79
|
-
|
|
42
|
+
currentEntrypointPath: string | null,
|
|
43
|
+
projectEntrypointPath: string | null,
|
|
80
44
|
isBootstrapped: boolean,
|
|
81
45
|
): HandoffDecision {
|
|
82
|
-
if (!
|
|
46
|
+
if (!projectEntrypointPath) {
|
|
83
47
|
return { shouldHandoff: false, runtimeMode: 'standalone' };
|
|
84
48
|
}
|
|
85
49
|
|
|
86
|
-
if (
|
|
50
|
+
if (sameEntrypoint(currentEntrypointPath, projectEntrypointPath)) {
|
|
87
51
|
return { shouldHandoff: false, runtimeMode: 'project' };
|
|
88
52
|
}
|
|
89
53
|
|
|
@@ -94,9 +58,8 @@ export function decideHandoff(
|
|
|
94
58
|
return { shouldHandoff: true, runtimeMode: 'project' };
|
|
95
59
|
}
|
|
96
60
|
|
|
97
|
-
function runProjectLocalServer(
|
|
98
|
-
const
|
|
99
|
-
const result = spawnSync(process.execPath, [entry, ...process.argv.slice(2)], {
|
|
61
|
+
function runProjectLocalServer(projectEntrypointPath: string): never {
|
|
62
|
+
const result = spawnSync(process.execPath, [projectEntrypointPath, ...process.argv.slice(2)], {
|
|
100
63
|
stdio: 'inherit',
|
|
101
64
|
env: {
|
|
102
65
|
...process.env,
|
|
@@ -112,16 +75,14 @@ function runProjectLocalServer(projectPackageJsonPath: string): never {
|
|
|
112
75
|
export function bootstrapProjectServer(): McpRuntimeMode {
|
|
113
76
|
const projectRoot = resolveProjectRoot();
|
|
114
77
|
const isBootstrapped = process.env.LETSRUNIT_MCP_BOOTSTRAPPED === '1';
|
|
115
|
-
const currentReq = createRequire(import.meta.url);
|
|
116
78
|
|
|
117
|
-
const
|
|
118
|
-
const projectEntryPath = resolveFromProject('@letsrunit/mcp-server', projectRoot);
|
|
119
|
-
const projectPackageJsonPath = toRealpath(findPackageJsonPath(projectEntryPath));
|
|
79
|
+
const currentEntryPath = toRealpath(fileURLToPath(import.meta.url));
|
|
80
|
+
const projectEntryPath = toRealpath(resolveFromProject('@letsrunit/mcp-server', projectRoot));
|
|
120
81
|
|
|
121
|
-
const decision = decideHandoff(
|
|
82
|
+
const decision = decideHandoff(currentEntryPath, projectEntryPath, isBootstrapped);
|
|
122
83
|
|
|
123
|
-
if (decision.shouldHandoff &&
|
|
124
|
-
runProjectLocalServer(
|
|
84
|
+
if (decision.shouldHandoff && projectEntryPath) {
|
|
85
|
+
runProjectLocalServer(projectEntryPath);
|
|
125
86
|
}
|
|
126
87
|
|
|
127
88
|
process.env.LETSRUNIT_MCP_RUNTIME_MODE = decision.runtimeMode;
|
package/src/index.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { createRequire } from 'module';
|
|
2
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
3
|
import { bootstrapProjectServer } from './bootstrap';
|
|
5
4
|
|
|
6
|
-
const
|
|
5
|
+
declare const __LETSRUNIT_VERSION__: string;
|
|
6
|
+
|
|
7
|
+
const version = typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';
|
|
7
8
|
bootstrapProjectServer();
|
|
8
9
|
|
|
9
10
|
const { SessionManager } = await import('./sessions');
|
|
@@ -38,7 +39,7 @@ registerListSteps(server, sessions);
|
|
|
38
39
|
registerListSessions(server, sessions);
|
|
39
40
|
registerDiff(server, sessions);
|
|
40
41
|
if (process.env.LETSRUNIT_MCP_DIAGNOSTICS === 'enabled') {
|
|
41
|
-
registerDiagnostics(server);
|
|
42
|
+
registerDiagnostics(server, sessions);
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
const transport = new StdioServerTransport();
|
package/src/tools/diagnostics.ts
CHANGED
|
@@ -1,19 +1,32 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { SessionManager } from '../sessions';
|
|
2
4
|
import { collectSupportDiagnostics } from '../utility/support';
|
|
3
5
|
import { err, text } from '../utility/response';
|
|
4
6
|
|
|
5
|
-
export function registerDiagnostics(server: McpServer): void {
|
|
7
|
+
export function registerDiagnostics(server: McpServer, sessions: SessionManager): void {
|
|
6
8
|
server.registerTool(
|
|
7
9
|
'letsrunit_diagnostics',
|
|
8
10
|
{
|
|
9
11
|
description:
|
|
10
12
|
'Return runtime diagnostics for MCP support-file loading (cwd resolution, cucumber config path, support entries). Available only when LETSRUNIT_MCP_DIAGNOSTICS=enabled.',
|
|
11
|
-
inputSchema: {
|
|
13
|
+
inputSchema: {
|
|
14
|
+
sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),
|
|
15
|
+
},
|
|
12
16
|
},
|
|
13
|
-
async () => {
|
|
17
|
+
async (input) => {
|
|
14
18
|
try {
|
|
15
19
|
const diagnostics = await collectSupportDiagnostics();
|
|
16
|
-
|
|
20
|
+
const session = sessions.get(input.sessionId);
|
|
21
|
+
const sessionInfo = {
|
|
22
|
+
sessionId: session.id,
|
|
23
|
+
createdAt: session.createdAt,
|
|
24
|
+
lastActivity: session.lastActivity,
|
|
25
|
+
stepCount: session.stepCount,
|
|
26
|
+
artifactDir: session.artifactDir,
|
|
27
|
+
pageUrl: session.controller.page.url(),
|
|
28
|
+
};
|
|
29
|
+
return text(JSON.stringify({ ...diagnostics, session: sessionInfo }));
|
|
17
30
|
} catch (e) {
|
|
18
31
|
return err(`Diagnostics failed: ${(e as Error).message}`);
|
|
19
32
|
}
|
package/src/utility/support.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
1
|
import { loadConfiguration } from '@cucumber/cucumber/api';
|
|
3
2
|
import { registry } from '@letsrunit/bdd';
|
|
4
3
|
import { existsSync, realpathSync } from 'node:fs';
|
|
5
4
|
import { glob } from 'node:fs/promises';
|
|
5
|
+
import { createRequire } from 'node:module';
|
|
6
6
|
import { isAbsolute, resolve } from 'node:path';
|
|
7
|
-
import { pathToFileURL } from 'node:url';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
|
+
import { decideHandoff } from '../bootstrap';
|
|
9
|
+
|
|
10
|
+
declare const __LETSRUNIT_VERSION__: string;
|
|
8
11
|
|
|
9
12
|
type CucumberConfig = {
|
|
10
13
|
require?: unknown;
|
|
@@ -14,9 +17,7 @@ type CucumberConfig = {
|
|
|
14
17
|
};
|
|
15
18
|
};
|
|
16
19
|
|
|
17
|
-
type SupportEntry =
|
|
18
|
-
| { kind: 'path'; value: string }
|
|
19
|
-
| { kind: 'module'; value: string };
|
|
20
|
+
type SupportEntry = { kind: 'path'; value: string } | { kind: 'module'; value: string };
|
|
20
21
|
|
|
21
22
|
const CUCUMBER_CONFIG_FILES = [
|
|
22
23
|
'cucumber.js',
|
|
@@ -44,16 +45,20 @@ export type SupportDiagnostics = {
|
|
|
44
45
|
loadedProjectRoots: string[];
|
|
45
46
|
loadedSupportEntries: string[];
|
|
46
47
|
mcpServer: {
|
|
47
|
-
|
|
48
|
+
version: string;
|
|
49
|
+
executablePath: string | null;
|
|
48
50
|
projectServerUsed: boolean;
|
|
51
|
+
handoffDecision: {
|
|
52
|
+
shouldHandoff: boolean;
|
|
53
|
+
runtimeMode: string;
|
|
54
|
+
};
|
|
49
55
|
serverMcpPath: string | null;
|
|
50
56
|
projectMcpPath: string | null;
|
|
51
|
-
sameModule: boolean;
|
|
52
57
|
};
|
|
58
|
+
letsrunitEnv: Record<string, string>;
|
|
53
59
|
moduleResolution: {
|
|
54
60
|
serverBddPath: string | null;
|
|
55
61
|
projectBddPath: string | null;
|
|
56
|
-
sameModule: boolean;
|
|
57
62
|
};
|
|
58
63
|
registry: {
|
|
59
64
|
total: number;
|
|
@@ -96,7 +101,7 @@ async function expandPathPatterns(baseDir: string, patterns: string[]): Promise<
|
|
|
96
101
|
|
|
97
102
|
for (const pattern of patterns) {
|
|
98
103
|
if (hasGlobMagic(pattern)) {
|
|
99
|
-
for await (const match of glob(pattern, { cwd: baseDir
|
|
104
|
+
for await (const match of glob(pattern, { cwd: baseDir })) {
|
|
100
105
|
files.add(normalizeMatch(baseDir, match));
|
|
101
106
|
}
|
|
102
107
|
continue;
|
|
@@ -113,7 +118,7 @@ async function resolveSupportEntries(baseDir: string, entries: string[]): Promis
|
|
|
113
118
|
|
|
114
119
|
for (const entry of entries) {
|
|
115
120
|
if (hasGlobMagic(entry)) {
|
|
116
|
-
for await (const match of glob(entry, { cwd: baseDir
|
|
121
|
+
for await (const match of glob(entry, { cwd: baseDir })) {
|
|
117
122
|
resolved.push({ kind: 'path', value: normalizeMatch(baseDir, match) });
|
|
118
123
|
}
|
|
119
124
|
continue;
|
|
@@ -171,6 +176,18 @@ function toRealpath(path: string | null): string | null {
|
|
|
171
176
|
}
|
|
172
177
|
}
|
|
173
178
|
|
|
179
|
+
function pickLetsrunitEnv(): Record<string, string> {
|
|
180
|
+
return Object.fromEntries(
|
|
181
|
+
Object.entries(process.env)
|
|
182
|
+
.filter(([key, value]) => key.startsWith('LETSRUNIT_') && typeof value === 'string')
|
|
183
|
+
.map(([key, value]) => [key, value as string]),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function resolveMcpServerVersion(): string {
|
|
188
|
+
return typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';
|
|
189
|
+
}
|
|
190
|
+
|
|
174
191
|
export async function collectSupportDiagnostics(cwd?: string): Promise<SupportDiagnostics> {
|
|
175
192
|
const effectiveCwd = resolveEffectiveCwd(cwd);
|
|
176
193
|
const projectRoot = resolve(effectiveCwd);
|
|
@@ -182,9 +199,18 @@ export async function collectSupportDiagnostics(cwd?: string): Promise<SupportDi
|
|
|
182
199
|
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
183
200
|
const serverBddPath = toRealpath(resolveFrom('@letsrunit/bdd', import.meta.url));
|
|
184
201
|
const projectBddPath = toRealpath(resolveFrom('@letsrunit/bdd', resolve(projectRoot, 'package.json')));
|
|
202
|
+
const projectMcpEntryPath = resolveFrom('@letsrunit/mcp-server', resolve(projectRoot, 'package.json'));
|
|
203
|
+
const currentEntrypointPath = toRealpath(fileURLToPath(import.meta.url));
|
|
204
|
+
const projectEntrypointPath = toRealpath(projectMcpEntryPath);
|
|
205
|
+
const handoffDecision = decideHandoff(
|
|
206
|
+
currentEntrypointPath,
|
|
207
|
+
projectEntrypointPath,
|
|
208
|
+
process.env.LETSRUNIT_MCP_BOOTSTRAPPED === '1',
|
|
209
|
+
);
|
|
185
210
|
const serverMcpPath = toRealpath(resolveFrom('@letsrunit/mcp-server', import.meta.url));
|
|
186
|
-
const projectMcpPath = toRealpath(
|
|
187
|
-
const
|
|
211
|
+
const projectMcpPath = toRealpath(projectMcpEntryPath);
|
|
212
|
+
const executablePath = toRealpath(process.argv[1] ?? null);
|
|
213
|
+
const version = resolveMcpServerVersion();
|
|
188
214
|
const registryDefinitions = registry.defs.map((def) => ({
|
|
189
215
|
type: def.type,
|
|
190
216
|
source: def.source,
|
|
@@ -205,16 +231,20 @@ export async function collectSupportDiagnostics(cwd?: string): Promise<SupportDi
|
|
|
205
231
|
loadedProjectRoots: [...loadedProjectRoots].sort(),
|
|
206
232
|
loadedSupportEntries: [...loadedSupportEntries].sort(),
|
|
207
233
|
mcpServer: {
|
|
208
|
-
|
|
209
|
-
|
|
234
|
+
version,
|
|
235
|
+
executablePath,
|
|
236
|
+
projectServerUsed: handoffDecision.runtimeMode === 'project',
|
|
237
|
+
handoffDecision: {
|
|
238
|
+
shouldHandoff: handoffDecision.shouldHandoff,
|
|
239
|
+
runtimeMode: handoffDecision.runtimeMode,
|
|
240
|
+
},
|
|
210
241
|
serverMcpPath,
|
|
211
242
|
projectMcpPath,
|
|
212
|
-
sameModule: !!serverMcpPath && !!projectMcpPath && serverMcpPath === projectMcpPath,
|
|
213
243
|
},
|
|
244
|
+
letsrunitEnv: pickLetsrunitEnv(),
|
|
214
245
|
moduleResolution: {
|
|
215
246
|
serverBddPath,
|
|
216
247
|
projectBddPath,
|
|
217
|
-
sameModule: !!serverBddPath && !!projectBddPath && serverBddPath === projectBddPath,
|
|
218
248
|
},
|
|
219
249
|
registry: {
|
|
220
250
|
total: registryDefinitions.length,
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utility/response.ts","../src/tools/debug.ts","../src/utility/support.ts","../src/tools/diagnostics.ts","../src/tools/diff.ts","../src/tools/list-steps.ts","../src/tools/list-sessions.ts","../src/utility/gherkin.ts","../src/tools/run.ts","../src/tools/screenshot.ts","../src/tools/session-close.ts","../src/tools/session-start.ts","../src/tools/snapshot.ts"],"names":["z","join"],"mappings":"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","file":"tools-DVAZVHS6.js","sourcesContent":["export function text(content: string) {\n return { content: [{ type: 'text' as const, text: content }] };\n}\n\nexport function err(message: string) {\n return { content: [{ type: 'text' as const, text: message }], isError: true };\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { text } from '../utility/response';\n\nexport function registerDebug(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_debug',\n {\n description:\n 'Evaluate JavaScript on the current page via Playwright page.evaluate(). Use for debugging — not for test logic.',\n inputSchema: {\n sessionId: z.string().describe('Session ID'),\n script: z.string().describe('JavaScript expression or function body to evaluate in the page context'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const result = await session.controller.page.evaluate(input.script);\n return text(JSON.stringify({ result }));\n } catch (e) {\n return text(JSON.stringify({ result: null, error: (e as Error).message }));\n }\n },\n );\n}\n","import { createRequire } from 'node:module';\nimport { loadConfiguration } from '@cucumber/cucumber/api';\nimport { registry } from '@letsrunit/bdd';\nimport { existsSync, realpathSync } from 'node:fs';\nimport { glob } from 'node:fs/promises';\nimport { isAbsolute, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\n\ntype CucumberConfig = {\n require?: unknown;\n import?: unknown;\n letsrunit?: {\n ignore?: unknown;\n };\n};\n\ntype SupportEntry =\n | { kind: 'path'; value: string }\n | { kind: 'module'; value: string };\n\nconst CUCUMBER_CONFIG_FILES = [\n 'cucumber.js',\n 'cucumber.mjs',\n 'cucumber.cjs',\n 'cucumber.ts',\n 'cucumber.mts',\n 'cucumber.cts',\n];\n\nconst loadedProjectRoots = new Set<string>();\nconst loadedSupportEntries = new Set<string>();\n\nexport type SupportDiagnostics = {\n envProjectCwd: string | null;\n processCwd: string;\n inputCwd: string | null;\n effectiveCwd: string;\n projectRoot: string;\n cucumberConfigPath: string | null;\n supportPatterns: string[];\n ignorePatterns: string[];\n ignoredPaths: string[];\n supportEntries: SupportEntry[];\n loadedProjectRoots: string[];\n loadedSupportEntries: string[];\n mcpServer: {\n runtimeMode: string;\n projectServerUsed: boolean;\n serverMcpPath: string | null;\n projectMcpPath: string | null;\n sameModule: boolean;\n };\n moduleResolution: {\n serverBddPath: string | null;\n projectBddPath: string | null;\n sameModule: boolean;\n };\n registry: {\n total: number;\n byType: {\n Given: number;\n When: number;\n Then: number;\n };\n definitions: Array<{\n type: 'Given' | 'When' | 'Then';\n source: string;\n comment?: string;\n }>;\n };\n};\n\nfunction toStrings(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n return value.filter((entry): entry is string => typeof entry === 'string');\n}\n\nfunction hasGlobMagic(input: string): boolean {\n return /[*?[\\]{}]/.test(input);\n}\n\nfunction isPathLike(input: string): boolean {\n return input.startsWith('.') || input.startsWith('/') || /^[A-Za-z]:[\\\\/]/.test(input);\n}\n\nfunction toAbsolutePath(baseDir: string, input: string): string {\n return isAbsolute(input) ? resolve(input) : resolve(baseDir, input);\n}\n\nfunction normalizeMatch(baseDir: string, match: string): string {\n return isAbsolute(match) ? resolve(match) : resolve(baseDir, match);\n}\n\nasync function expandPathPatterns(baseDir: string, patterns: string[]): Promise<Set<string>> {\n const files = new Set<string>();\n\n for (const pattern of patterns) {\n if (hasGlobMagic(pattern)) {\n for await (const match of glob(pattern, { cwd: baseDir, absolute: true, withFileTypes: false })) {\n files.add(normalizeMatch(baseDir, match));\n }\n continue;\n }\n\n files.add(toAbsolutePath(baseDir, pattern));\n }\n\n return files;\n}\n\nasync function resolveSupportEntries(baseDir: string, entries: string[]): Promise<SupportEntry[]> {\n const resolved: SupportEntry[] = [];\n\n for (const entry of entries) {\n if (hasGlobMagic(entry)) {\n for await (const match of glob(entry, { cwd: baseDir, absolute: true, withFileTypes: false })) {\n resolved.push({ kind: 'path', value: normalizeMatch(baseDir, match) });\n }\n continue;\n }\n\n if (!isPathLike(entry)) {\n resolved.push({ kind: 'module', value: entry });\n continue;\n }\n\n resolved.push({ kind: 'path', value: toAbsolutePath(baseDir, entry) });\n }\n\n return resolved;\n}\n\nfunction findCucumberConfig(cwd: string): string | null {\n for (const filename of CUCUMBER_CONFIG_FILES) {\n const path = resolve(cwd, filename);\n if (existsSync(path)) return path;\n }\n\n return null;\n}\n\nasync function loadLetsrunitIgnorePatterns(cwd: string): Promise<string[]> {\n const configPath = findCucumberConfig(cwd);\n if (!configPath) return [];\n\n const configModule = await import(pathToFileURL(configPath).href);\n const config = (configModule.default ?? configModule) as CucumberConfig;\n\n return toStrings(config.letsrunit?.ignore);\n}\n\nfunction resolveEffectiveCwd(cwd?: string): string {\n return cwd ?? process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd();\n}\n\nfunction resolveFrom(moduleId: string, fromPath: string): string | null {\n try {\n const req = createRequire(fromPath);\n return req.resolve(moduleId);\n } catch {\n return null;\n }\n}\n\nfunction toRealpath(path: string | null): string | null {\n if (!path) return null;\n try {\n return realpathSync(path);\n } catch {\n return path;\n }\n}\n\nexport async function collectSupportDiagnostics(cwd?: string): Promise<SupportDiagnostics> {\n const effectiveCwd = resolveEffectiveCwd(cwd);\n const projectRoot = resolve(effectiveCwd);\n const cucumberConfigPath = findCucumberConfig(projectRoot);\n const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });\n const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];\n const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);\n const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);\n const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);\n const serverBddPath = toRealpath(resolveFrom('@letsrunit/bdd', import.meta.url));\n const projectBddPath = toRealpath(resolveFrom('@letsrunit/bdd', resolve(projectRoot, 'package.json')));\n const serverMcpPath = toRealpath(resolveFrom('@letsrunit/mcp-server', import.meta.url));\n const projectMcpPath = toRealpath(resolveFrom('@letsrunit/mcp-server', resolve(projectRoot, 'package.json')));\n const runtimeMode = process.env.LETSRUNIT_MCP_RUNTIME_MODE ?? 'standalone';\n const registryDefinitions = registry.defs.map((def) => ({\n type: def.type,\n source: def.source,\n comment: def.comment,\n }));\n\n return {\n envProjectCwd: process.env.LETSRUNIT_PROJECT_CWD ?? null,\n processCwd: process.cwd(),\n inputCwd: cwd ?? null,\n effectiveCwd,\n projectRoot,\n cucumberConfigPath,\n supportPatterns,\n ignorePatterns,\n ignoredPaths: [...ignoredPaths].sort(),\n supportEntries,\n loadedProjectRoots: [...loadedProjectRoots].sort(),\n loadedSupportEntries: [...loadedSupportEntries].sort(),\n mcpServer: {\n runtimeMode,\n projectServerUsed: runtimeMode === 'project',\n serverMcpPath,\n projectMcpPath,\n sameModule: !!serverMcpPath && !!projectMcpPath && serverMcpPath === projectMcpPath,\n },\n moduleResolution: {\n serverBddPath,\n projectBddPath,\n sameModule: !!serverBddPath && !!projectBddPath && serverBddPath === projectBddPath,\n },\n registry: {\n total: registryDefinitions.length,\n byType: {\n Given: registryDefinitions.filter((d) => d.type === 'Given').length,\n When: registryDefinitions.filter((d) => d.type === 'When').length,\n Then: registryDefinitions.filter((d) => d.type === 'Then').length,\n },\n definitions: registryDefinitions,\n },\n };\n}\n\nexport async function loadSupportFiles(cwd?: string): Promise<void> {\n const projectRoot = resolve(resolveEffectiveCwd(cwd));\n if (loadedProjectRoots.has(projectRoot)) return;\n\n const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });\n const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];\n if (supportPatterns.length === 0) {\n loadedProjectRoots.add(projectRoot);\n return;\n }\n\n const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);\n const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);\n const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);\n\n for (const entry of supportEntries) {\n if (entry.kind === 'path' && ignoredPaths.has(entry.value)) {\n continue;\n }\n\n const key = `${entry.kind}:${entry.value}`;\n if (loadedSupportEntries.has(key)) continue;\n\n if (entry.kind === 'path') {\n await import(pathToFileURL(entry.value).href);\n } else {\n await import(entry.value);\n }\n\n loadedSupportEntries.add(key);\n }\n\n loadedProjectRoots.add(projectRoot);\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { collectSupportDiagnostics } from '../utility/support';\nimport { err, text } from '../utility/response';\n\nexport function registerDiagnostics(server: McpServer): void {\n server.registerTool(\n 'letsrunit_diagnostics',\n {\n description:\n 'Return runtime diagnostics for MCP support-file loading (cwd resolution, cucumber config path, support entries). Available only when LETSRUNIT_MCP_DIAGNOSTICS=enabled.',\n inputSchema: {},\n },\n async () => {\n try {\n const diagnostics = await collectSupportDiagnostics();\n return text(JSON.stringify(diagnostics));\n } catch (e) {\n return err(`Diagnostics failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { unifiedHtmlDiff } from '@letsrunit/playwright';\nimport { openStore, findLastTest, findArtifacts } from '@letsrunit/store';\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nconst DEFAULT_DB_PATH = join(process.cwd(), '.letsrunit', 'letsrunit.db');\n\nfunction getDbPath(): string {\n return process.env.LETSRUNIT_DB_PATH ?? DEFAULT_DB_PATH;\n}\n\nfunction resolveAllowedCommits(): string[] | undefined {\n try {\n const output = execSync('git log --format=%H', { encoding: 'utf8' });\n return output.trim().split('\\n').filter(Boolean);\n } catch {\n return undefined;\n }\n}\n\nexport function registerDiff(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_diff',\n {\n description:\n 'Diff the current live page against the HTML snapshot from the last passing test of a scenario. ' +\n 'Pass the scenarioId returned by letsrunit_run. ' +\n 'Returns a unified HTML diff and paths to baseline screenshots. ' +\n 'By default only considers baseline tests from the current git ancestry (gitTreeOnly: true).',\n inputSchema: {\n sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),\n scenarioId: z.string().describe('Scenario UUID returned by letsrunit_run'),\n gitTreeOnly: z\n .boolean()\n .optional()\n .describe('Restrict baseline to tests from the current git ancestry (default: true)'),\n },\n },\n async (input) => {\n const dbPath = getDbPath();\n const artifactDir = join(dirname(dbPath), 'artifacts');\n let db: ReturnType<typeof openStore> | undefined;\n\n try {\n try {\n db = openStore(dbPath);\n } catch {\n return err('Could not open the letsrunit store. Run cucumber with the store formatter first.');\n }\n\n const allowedCommits = (input.gitTreeOnly ?? true) ? resolveAllowedCommits() : undefined;\n\n const test = findLastTest(db, input.scenarioId, 'passed', allowedCommits ?? undefined);\n if (!test) {\n return err(\n allowedCommits\n ? 'No passing test found for this scenario in the current git ancestry. Try gitTreeOnly: false or run cucumber first.'\n : 'No passing test found for this scenario.',\n );\n }\n\n const artifacts = findArtifacts(db, test.id);\n\n const htmlArtifact = [...artifacts].reverse().find((a) => a.filename.endsWith('.html'));\n if (!htmlArtifact) {\n return err('No HTML snapshot found in the baseline test. Ensure the store formatter is configured.');\n }\n\n const storedHtml = readFileSync(join(artifactDir, htmlArtifact.filename), 'utf-8');\n\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const diff = await unifiedHtmlDiff({ html: storedHtml, url: 'about:blank' }, session.controller.page);\n\n const screenshots = artifacts\n .filter((a) => a.stepIdx === htmlArtifact.stepIdx && a.filename.endsWith('.png'))\n .map((a) => join(artifactDir, a.filename));\n\n return text(\n JSON.stringify({\n diff,\n baseline: {\n testId: test.id,\n commit: test.gitCommit,\n screenshots,\n },\n }),\n );\n } catch (e) {\n return err(`Diff failed: ${(e as Error).message}`);\n } finally {\n db?.close();\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nconst stepTypeSchema = z.enum(['Given', 'When', 'Then']);\n\nexport function registerListSteps(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_list_steps',\n {\n description:\n 'List available step definitions for a session. Optionally filter by step type (Given/When/Then).',\n inputSchema: {\n sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),\n type: stepTypeSchema.optional().describe('Optional step type filter'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const steps = session.controller.listSteps(input.type);\n return text(JSON.stringify({ steps }));\n } catch (e) {\n return err(`List steps failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { type SessionManager } from '../sessions';\nimport { text } from '../utility/response';\n\nexport function registerListSessions(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_list_sessions',\n {\n description: 'List all active browser sessions.',\n inputSchema: {},\n },\n async () => {\n const list = sessions.list().map((s) => ({\n sessionId: s.id,\n createdAt: s.createdAt,\n lastActivity: s.lastActivity,\n stepCount: s.stepCount,\n artifactDir: s.artifactDir,\n }));\n\n return text(JSON.stringify({ sessions: list }));\n },\n );\n}\n","export function normalizeGherkin(input: string): string {\n const trimmed = input.trim();\n\n if (/^(Feature|Scenario|Background):/im.test(trimmed)) {\n return trimmed;\n }\n\n return `Feature: MCP\\n\\nScenario: Steps\\n ${trimmed.split('\\n').join('\\n ')}`;\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport { scenarioIdFromGherkin } from '@letsrunit/gherkin';\nimport type { SessionManager } from '../sessions';\nimport { normalizeGherkin } from '../utility/gherkin';\nimport { err, text } from '../utility/response';\n\nexport function registerRun(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_run',\n {\n description:\n 'Execute Gherkin steps or a complete feature in the browser. ' +\n 'Accepts a single step line, multiple step lines, a full Scenario, or a full Feature. ' +\n 'Returns status, steps, reason on failure, and journal entries. Does not return a page snapshot — call letsrunit_snapshot explicitly if you need the DOM.',\n inputSchema: {\n sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),\n input: z\n .string()\n .describe(\n 'Gherkin text to execute: one or more step lines (e.g. \"Given I am on \\\\\"https://example.com\\\\\"\"), a Scenario block, or a full Feature block.',\n ),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const feature = normalizeGherkin(input.input);\n\n session.sink.clear();\n const result = await session.controller.run(feature);\n session.stepCount += result.steps.length;\n\n return text(\n JSON.stringify({\n status: result.status,\n steps: result.steps,\n reason: result.reason?.message,\n journal: session.sink.getEntries(),\n scenarioId: scenarioIdFromGherkin(input.input),\n }),\n );\n } catch (e) {\n return err(`Run failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { screenshot, screenshotElement } from '@letsrunit/playwright';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { mkdir, writeFile } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nexport function registerScreenshot(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_screenshot',\n {\n description:\n 'Take a screenshot of the current page. Optionally crop to a specific element (selector) or highlight elements before capturing (mask).',\n inputSchema: {\n sessionId: z.string().describe('Session ID'),\n selector: z\n .string()\n .optional()\n .describe('CSS selector — crop screenshot to the bounding box of this element'),\n mask: z\n .array(z.string())\n .optional()\n .describe('CSS selectors whose matching elements are highlighted (dark overlay, element spotlighted).'),\n fullPage: z.boolean().optional().describe('Capture the full scrollable page (default: false)'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const page = session.controller.page;\n\n let file: File;\n\n if (input.selector) {\n file = await screenshotElement(page, input.selector);\n } else {\n const masks = input.mask?.map((sel) => page.locator(sel)) ?? [];\n file = await screenshot(page, {\n fullPage: input.fullPage ?? false,\n ...(masks.length ? { mask: masks } : {}),\n });\n }\n\n await mkdir(session.artifactDir, { recursive: true });\n const path = join(session.artifactDir, file.name);\n await writeFile(path, await file.bytes());\n\n return text(JSON.stringify({ path, mimeType: 'image/png' }));\n } catch (e) {\n return err(`Screenshot failed: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nexport function registerSessionClose(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_session_close',\n {\n description: 'Close a browser session and release its resources.',\n inputSchema: {\n sessionId: z.string().describe('Session ID to close'),\n },\n },\n async (input) => {\n try {\n await sessions.close(input.sessionId);\n return text(JSON.stringify({ closed: true }));\n } catch (e) {\n return err(`Failed to close session: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\nimport { loadSupportFiles } from '../utility/support';\n\nexport function registerSessionStart(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_session_start',\n {\n description:\n 'Launch a new browser session. Does not navigate anywhere — use letsrunit_run with a Given step to navigate. Set baseURL to enable relative paths like \"Given I\\'m on the homepage\".',\n inputSchema: {\n baseURL: z.string().optional().describe('Base URL for the session, e.g. \"http://localhost:3000\". Enables relative paths in Given steps like \"Given I\\'m on the homepage\" or \"Given I\\'m on page \\\\\"/login\\\\\"\"'),\n language: z.string().optional().describe(\"Browser language code, e.g. 'en', 'fr'\"),\n headless: z.boolean().optional().describe('Run browser in headless mode (default: true)'),\n viewportWidth: z.number().int().optional().describe('Viewport width in pixels (default: 1280)'),\n viewportHeight: z.number().int().optional().describe('Viewport height in pixels (default: 720)'),\n },\n },\n async (input) => {\n try {\n if (process.env.LETSRUNIT_MCP_RUNTIME_MODE === 'project') {\n await loadSupportFiles();\n }\n\n const viewport =\n input.viewportWidth || input.viewportHeight\n ? { width: input.viewportWidth ?? 1280, height: input.viewportHeight ?? 720 }\n : undefined;\n\n const session = await sessions.create({\n baseURL: input.baseURL,\n headless: input.headless ?? true,\n locale: input.language,\n viewport,\n });\n\n return text(JSON.stringify({ sessionId: session.id }));\n } catch (e) {\n return err(`Failed to start session: ${(e as Error).message}`);\n }\n },\n );\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { scrubHtml } from '@letsrunit/playwright';\nimport { z } from 'zod';\nimport { type SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\n\nconst stripAttributesSchema = z.nativeEnum({\n none: 0,\n semantic: 1,\n aggressive: 2,\n});\n\nexport function registerSnapshot(server: McpServer, sessions: SessionManager): void {\n server.registerTool(\n 'letsrunit_snapshot',\n {\n description:\n 'Get the current page HTML, scrubbed for LLM consumption. Use selector to scope to a DOM subtree.',\n inputSchema: {\n sessionId: z.string().describe('Session ID'),\n selector: z\n .string()\n .optional()\n .describe(\"CSS selector — return only the matching element's outer HTML instead of the full page\"),\n dropHidden: z.boolean().optional().describe('Remove hidden/inert nodes (default: true)'),\n dropHead: z.boolean().optional().describe('Remove the <head> element (default: true)'),\n pickMain: z.boolean().optional().describe('Keep only the <main> element (default: auto)'),\n stripAttributes: stripAttributesSchema\n .optional()\n .describe('Attribute allowlist level: 0=none, 1=semantic (default), 2=aggressive'),\n },\n },\n async (input) => {\n try {\n const session = sessions.get(input.sessionId);\n sessions.touch(input.sessionId);\n\n const page = session.controller.page;\n const url = page.url();\n\n const opts = {\n dropHidden: input.dropHidden,\n dropHead: input.dropHead,\n pickMain: input.pickMain,\n stripAttributes: input.stripAttributes,\n };\n\n let html: string;\n\n if (input.selector) {\n const rawHtml = await page\n .locator(input.selector)\n .first()\n .evaluate((el: Element) => el.outerHTML);\n html = await scrubHtml({ html: rawHtml, url }, opts);\n } else {\n html = await scrubHtml(page, opts);\n }\n\n return text(JSON.stringify({ url, html }));\n } catch (e) {\n return err(`Snapshot failed: ${(e as Error).message}`);\n }\n },\n );\n}\n"]}
|