@letsrunit/mcp-server 0.14.4 → 0.14.5
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 → chunk-CEICWMN3.js} +21 -11
- package/dist/chunk-CEICWMN3.js.map +1 -0
- package/dist/index.js +5 -5
- package/dist/index.js.map +1 -1
- package/dist/{tools-CNEENZR2.js → tools-3QWUX4W5.js} +52 -44
- package/dist/tools-3QWUX4W5.js.map +1 -0
- package/package.json +8 -8
- package/src/bootstrap.ts +20 -9
- package/src/index.ts +3 -3
- package/src/tools/diagnostics.ts +2 -2
- package/src/tools/session-start.ts +13 -3
- package/src/utility/diagnostics.ts +159 -0
- package/src/utility/support.ts +9 -151
- package/dist/chunk-XJCBPTOU.js.map +0 -1
- package/dist/tools-CNEENZR2.js.map +0 -1
|
@@ -30,16 +30,28 @@ function sameEntrypoint(a, b) {
|
|
|
30
30
|
if (!a || !b) return false;
|
|
31
31
|
return a === b;
|
|
32
32
|
}
|
|
33
|
-
function
|
|
33
|
+
function resolveRuntimeModeOverride() {
|
|
34
|
+
const runtimeMode = process.env.LETSRUNIT_MCP_RUNTIME_MODE;
|
|
35
|
+
if (runtimeMode == null || runtimeMode === "") {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
if (runtimeMode === "project" || runtimeMode === "standalone") {
|
|
39
|
+
return runtimeMode;
|
|
40
|
+
}
|
|
41
|
+
throw new Error(
|
|
42
|
+
`Invalid LETSRUNIT_MCP_RUNTIME_MODE: ${runtimeMode}. Expected "project" or "standalone".`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
function decideHandoff(currentEntrypointPath, projectEntrypointPath, runtimeModeOverride) {
|
|
46
|
+
if (runtimeModeOverride) {
|
|
47
|
+
return { shouldHandoff: false, runtimeMode: runtimeModeOverride };
|
|
48
|
+
}
|
|
34
49
|
if (!projectEntrypointPath) {
|
|
35
50
|
return { shouldHandoff: false, runtimeMode: "standalone" };
|
|
36
51
|
}
|
|
37
52
|
if (sameEntrypoint(currentEntrypointPath, projectEntrypointPath)) {
|
|
38
53
|
return { shouldHandoff: false, runtimeMode: "project" };
|
|
39
54
|
}
|
|
40
|
-
if (isBootstrapped) {
|
|
41
|
-
return { shouldHandoff: false, runtimeMode: "standalone" };
|
|
42
|
-
}
|
|
43
55
|
return { shouldHandoff: true, runtimeMode: "project" };
|
|
44
56
|
}
|
|
45
57
|
function runProjectLocalServer(projectEntrypointPath) {
|
|
@@ -47,7 +59,6 @@ function runProjectLocalServer(projectEntrypointPath) {
|
|
|
47
59
|
stdio: "inherit",
|
|
48
60
|
env: {
|
|
49
61
|
...process.env,
|
|
50
|
-
LETSRUNIT_MCP_BOOTSTRAPPED: "1",
|
|
51
62
|
LETSRUNIT_MCP_RUNTIME_MODE: "project"
|
|
52
63
|
}
|
|
53
64
|
});
|
|
@@ -56,17 +67,16 @@ function runProjectLocalServer(projectEntrypointPath) {
|
|
|
56
67
|
}
|
|
57
68
|
function bootstrapProjectServer() {
|
|
58
69
|
const projectRoot = resolveProjectRoot();
|
|
59
|
-
const
|
|
70
|
+
const runtimeModeOverride = resolveRuntimeModeOverride();
|
|
60
71
|
const currentEntryPath = toRealpath(fileURLToPath(import.meta.url));
|
|
61
72
|
const projectEntryPath = toRealpath(resolveFromProject("@letsrunit/mcp-server", projectRoot));
|
|
62
|
-
const decision = decideHandoff(currentEntryPath, projectEntryPath,
|
|
73
|
+
const decision = decideHandoff(currentEntryPath, projectEntryPath, runtimeModeOverride);
|
|
63
74
|
if (decision.shouldHandoff && projectEntryPath) {
|
|
64
75
|
runProjectLocalServer(projectEntryPath);
|
|
65
76
|
}
|
|
66
|
-
process.env.LETSRUNIT_MCP_RUNTIME_MODE = decision.runtimeMode;
|
|
67
77
|
return decision.runtimeMode;
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
export { bootstrapProjectServer, decideHandoff };
|
|
71
|
-
//# sourceMappingURL=chunk-
|
|
72
|
-
//# sourceMappingURL=chunk-
|
|
80
|
+
export { bootstrapProjectServer, decideHandoff, resolveRuntimeModeOverride };
|
|
81
|
+
//# sourceMappingURL=chunk-CEICWMN3.js.map
|
|
82
|
+
//# sourceMappingURL=chunk-CEICWMN3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/bootstrap.ts"],"names":[],"mappings":"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","file":"chunk-CEICWMN3.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 resolveRuntimeModeOverride(): McpRuntimeMode | null {\n const runtimeMode = process.env.LETSRUNIT_MCP_RUNTIME_MODE;\n if (runtimeMode == null || runtimeMode === '') {\n return null;\n }\n if (runtimeMode === 'project' || runtimeMode === 'standalone') {\n return runtimeMode;\n }\n throw new Error(\n `Invalid LETSRUNIT_MCP_RUNTIME_MODE: ${runtimeMode}. Expected \"project\" or \"standalone\".`,\n );\n}\n\nexport function decideHandoff(\n currentEntrypointPath: string | null,\n projectEntrypointPath: string | null,\n runtimeModeOverride: McpRuntimeMode | null,\n): HandoffDecision {\n if (runtimeModeOverride) {\n return { shouldHandoff: false, runtimeMode: runtimeModeOverride };\n }\n\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 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_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 runtimeModeOverride = resolveRuntimeModeOverride();\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, runtimeModeOverride);\n\n if (decision.shouldHandoff && projectEntryPath) {\n runProjectLocalServer(projectEntryPath);\n }\n\n return decision.runtimeMode;\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as __m from 'node:module';
|
|
3
|
-
import { bootstrapProjectServer } from './chunk-
|
|
3
|
+
import { bootstrapProjectServer } from './chunk-CEICWMN3.js';
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
6
|
|
|
7
7
|
__m.createRequire(import.meta.url);
|
|
8
|
-
var version = "0.14.
|
|
9
|
-
bootstrapProjectServer();
|
|
8
|
+
var version = "0.14.5" ;
|
|
9
|
+
var runtimeMode = bootstrapProjectServer();
|
|
10
10
|
var { SessionManager } = await import('./sessions-BYH3NJQG.js');
|
|
11
11
|
var {
|
|
12
12
|
registerDebug,
|
|
@@ -19,14 +19,14 @@ var {
|
|
|
19
19
|
registerSessionClose,
|
|
20
20
|
registerSessionStart,
|
|
21
21
|
registerSnapshot
|
|
22
|
-
} = await import('./tools-
|
|
22
|
+
} = await import('./tools-3QWUX4W5.js');
|
|
23
23
|
var sessions = new SessionManager();
|
|
24
24
|
var server = new McpServer({
|
|
25
25
|
name: "letsrunit",
|
|
26
26
|
version,
|
|
27
27
|
websiteUrl: "https://letsrunit.ai"
|
|
28
28
|
});
|
|
29
|
-
registerSessionStart(server, sessions);
|
|
29
|
+
registerSessionStart(server, sessions, { runtimeMode });
|
|
30
30
|
registerRun(server, sessions);
|
|
31
31
|
registerSnapshot(server, sessions);
|
|
32
32
|
registerScreenshot(server, sessions);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;AAMA,IAAM,OAAA,GAAsD,QAAA,CAAwB;AACpF,sBAAA,EAAuB;
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;AAMA,IAAM,OAAA,GAAsD,QAAA,CAAwB;AACpF,IAAM,cAAc,sBAAA,EAAuB;AAE3C,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,MAAA,EAAQ,QAAA,EAAU,EAAE,WAAA,EAAa,CAAA;AACtD,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 | undefined;\n\nconst version = typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';\nconst runtimeMode = bootstrapProjectServer();\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, { runtimeMode });\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,14 +1,14 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import * as __m from 'node:module';
|
|
3
|
-
import { decideHandoff } from './chunk-
|
|
3
|
+
import { decideHandoff, resolveRuntimeModeOverride } from './chunk-CEICWMN3.js';
|
|
4
4
|
import { z } from 'zod';
|
|
5
5
|
import { loadConfiguration } from '@cucumber/cucumber/api';
|
|
6
6
|
import { registry } from '@letsrunit/bdd';
|
|
7
7
|
import { readFileSync, existsSync, realpathSync } from 'fs';
|
|
8
|
-
import { mkdir, writeFile, glob } from 'fs/promises';
|
|
9
8
|
import { createRequire } from 'module';
|
|
10
9
|
import { join, dirname, resolve, isAbsolute } from 'path';
|
|
11
10
|
import { fileURLToPath, pathToFileURL } from 'url';
|
|
11
|
+
import { mkdir, writeFile, glob } from 'fs/promises';
|
|
12
12
|
import { unifiedHtmlDiff, screenshotElement, screenshot, scrubHtml } from '@letsrunit/playwright';
|
|
13
13
|
import { openStore, findLastTest, findArtifacts } from '@letsrunit/store';
|
|
14
14
|
import { execSync } from 'child_process';
|
|
@@ -120,6 +120,41 @@ async function loadLetsrunitIgnorePatterns(cwd) {
|
|
|
120
120
|
function resolveEffectiveCwd(cwd) {
|
|
121
121
|
return cwd ?? process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd();
|
|
122
122
|
}
|
|
123
|
+
function getSupportLoadState() {
|
|
124
|
+
return {
|
|
125
|
+
loadedProjectRoots: [...loadedProjectRoots].sort(),
|
|
126
|
+
loadedSupportEntries: [...loadedSupportEntries].sort()
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
async function loadSupportFiles(cwd) {
|
|
130
|
+
const projectRoot = resolve(resolveEffectiveCwd(cwd));
|
|
131
|
+
if (loadedProjectRoots.has(projectRoot)) return;
|
|
132
|
+
const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });
|
|
133
|
+
const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];
|
|
134
|
+
if (supportPatterns.length === 0) {
|
|
135
|
+
loadedProjectRoots.add(projectRoot);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);
|
|
139
|
+
const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);
|
|
140
|
+
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
141
|
+
for (const entry of supportEntries) {
|
|
142
|
+
if (entry.kind === "path" && ignoredPaths.has(entry.value)) {
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
const key = `${entry.kind}:${entry.value}`;
|
|
146
|
+
if (loadedSupportEntries.has(key)) continue;
|
|
147
|
+
if (entry.kind === "path") {
|
|
148
|
+
await import(pathToFileURL(entry.value).href);
|
|
149
|
+
} else {
|
|
150
|
+
await import(entry.value);
|
|
151
|
+
}
|
|
152
|
+
loadedSupportEntries.add(key);
|
|
153
|
+
}
|
|
154
|
+
loadedProjectRoots.add(projectRoot);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// src/utility/diagnostics.ts
|
|
123
158
|
function resolveFrom(moduleId, fromPath) {
|
|
124
159
|
try {
|
|
125
160
|
const req = createRequire(fromPath);
|
|
@@ -141,18 +176,16 @@ function pickLetsrunitEnv() {
|
|
|
141
176
|
Object.entries(process.env).filter(([key, value]) => key.startsWith("LETSRUNIT_") && typeof value === "string").map(([key, value]) => [key, value])
|
|
142
177
|
);
|
|
143
178
|
}
|
|
144
|
-
function
|
|
145
|
-
return "0.14.4" ;
|
|
146
|
-
}
|
|
147
|
-
async function collectSupportDiagnostics(cwd) {
|
|
179
|
+
async function collectDiagnostics(cwd) {
|
|
148
180
|
const effectiveCwd = resolveEffectiveCwd(cwd);
|
|
149
181
|
const projectRoot = resolve(effectiveCwd);
|
|
150
182
|
const cucumberConfigPath = findCucumberConfig(projectRoot);
|
|
151
183
|
const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });
|
|
152
|
-
const supportPatterns = [...
|
|
184
|
+
const supportPatterns = [...useConfiguration.require ?? [], ...useConfiguration.import ?? []];
|
|
153
185
|
const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);
|
|
154
186
|
const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);
|
|
155
187
|
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
188
|
+
const supportLoadState = getSupportLoadState();
|
|
156
189
|
const serverBddPath = toRealpath(resolveFrom("@letsrunit/bdd", import.meta.url));
|
|
157
190
|
const projectBddPath = toRealpath(resolveFrom("@letsrunit/bdd", resolve(projectRoot, "package.json")));
|
|
158
191
|
const projectMcpEntryPath = resolveFrom("@letsrunit/mcp-server", resolve(projectRoot, "package.json"));
|
|
@@ -161,12 +194,12 @@ async function collectSupportDiagnostics(cwd) {
|
|
|
161
194
|
const handoffDecision = decideHandoff(
|
|
162
195
|
currentEntrypointPath,
|
|
163
196
|
projectEntrypointPath,
|
|
164
|
-
|
|
197
|
+
resolveRuntimeModeOverride()
|
|
165
198
|
);
|
|
166
199
|
const serverMcpPath = toRealpath(resolveFrom("@letsrunit/mcp-server", import.meta.url));
|
|
167
200
|
const projectMcpPath = toRealpath(projectMcpEntryPath);
|
|
168
201
|
const executablePath = toRealpath(process.argv[1] ?? null);
|
|
169
|
-
const version =
|
|
202
|
+
const version = "0.14.5" ;
|
|
170
203
|
const registryDefinitions = registry.defs.map((def) => ({
|
|
171
204
|
type: def.type,
|
|
172
205
|
source: def.source,
|
|
@@ -183,8 +216,8 @@ async function collectSupportDiagnostics(cwd) {
|
|
|
183
216
|
ignorePatterns,
|
|
184
217
|
ignoredPaths: [...ignoredPaths].sort(),
|
|
185
218
|
supportEntries,
|
|
186
|
-
loadedProjectRoots:
|
|
187
|
-
loadedSupportEntries:
|
|
219
|
+
loadedProjectRoots: supportLoadState.loadedProjectRoots,
|
|
220
|
+
loadedSupportEntries: supportLoadState.loadedSupportEntries,
|
|
188
221
|
mcpServer: {
|
|
189
222
|
version,
|
|
190
223
|
executablePath,
|
|
@@ -212,33 +245,6 @@ async function collectSupportDiagnostics(cwd) {
|
|
|
212
245
|
}
|
|
213
246
|
};
|
|
214
247
|
}
|
|
215
|
-
async function loadSupportFiles(cwd) {
|
|
216
|
-
const projectRoot = resolve(resolveEffectiveCwd(cwd));
|
|
217
|
-
if (loadedProjectRoots.has(projectRoot)) return;
|
|
218
|
-
const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });
|
|
219
|
-
const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];
|
|
220
|
-
if (supportPatterns.length === 0) {
|
|
221
|
-
loadedProjectRoots.add(projectRoot);
|
|
222
|
-
return;
|
|
223
|
-
}
|
|
224
|
-
const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);
|
|
225
|
-
const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);
|
|
226
|
-
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
227
|
-
for (const entry of supportEntries) {
|
|
228
|
-
if (entry.kind === "path" && ignoredPaths.has(entry.value)) {
|
|
229
|
-
continue;
|
|
230
|
-
}
|
|
231
|
-
const key = `${entry.kind}:${entry.value}`;
|
|
232
|
-
if (loadedSupportEntries.has(key)) continue;
|
|
233
|
-
if (entry.kind === "path") {
|
|
234
|
-
await import(pathToFileURL(entry.value).href);
|
|
235
|
-
} else {
|
|
236
|
-
await import(entry.value);
|
|
237
|
-
}
|
|
238
|
-
loadedSupportEntries.add(key);
|
|
239
|
-
}
|
|
240
|
-
loadedProjectRoots.add(projectRoot);
|
|
241
|
-
}
|
|
242
248
|
|
|
243
249
|
// src/tools/diagnostics.ts
|
|
244
250
|
function registerDiagnostics(server, sessions) {
|
|
@@ -252,7 +258,7 @@ function registerDiagnostics(server, sessions) {
|
|
|
252
258
|
},
|
|
253
259
|
async (input) => {
|
|
254
260
|
try {
|
|
255
|
-
const diagnostics = await
|
|
261
|
+
const diagnostics = await collectDiagnostics();
|
|
256
262
|
const session = sessions.get(input.sessionId);
|
|
257
263
|
const sessionInfo = {
|
|
258
264
|
sessionId: session.id,
|
|
@@ -486,13 +492,15 @@ function registerSessionClose(server, sessions) {
|
|
|
486
492
|
}
|
|
487
493
|
);
|
|
488
494
|
}
|
|
489
|
-
function registerSessionStart(server, sessions) {
|
|
495
|
+
function registerSessionStart(server, sessions, opts) {
|
|
490
496
|
server.registerTool(
|
|
491
497
|
"letsrunit_session_start",
|
|
492
498
|
{
|
|
493
499
|
description: `Launch a new browser session. Does not navigate anywhere \u2014 use letsrunit_run with a Given step to navigate. Set baseURL to enable relative paths like "Given I'm on the homepage".`,
|
|
494
500
|
inputSchema: {
|
|
495
|
-
baseURL: z.string().optional().describe(
|
|
501
|
+
baseURL: z.string().optional().describe(
|
|
502
|
+
`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\\""`
|
|
503
|
+
),
|
|
496
504
|
language: z.string().optional().describe("Browser language code, e.g. 'en', 'fr'"),
|
|
497
505
|
headless: z.boolean().optional().describe("Run browser in headless mode (default: true)"),
|
|
498
506
|
viewportWidth: z.number().int().optional().describe("Viewport width in pixels (default: 1280)"),
|
|
@@ -501,7 +509,7 @@ function registerSessionStart(server, sessions) {
|
|
|
501
509
|
},
|
|
502
510
|
async (input) => {
|
|
503
511
|
try {
|
|
504
|
-
if (
|
|
512
|
+
if (opts?.runtimeMode === "project") {
|
|
505
513
|
await loadSupportFiles();
|
|
506
514
|
}
|
|
507
515
|
const viewport = input.viewportWidth || input.viewportHeight ? { width: input.viewportWidth ?? 1280, height: input.viewportHeight ?? 720 } : void 0;
|
|
@@ -565,5 +573,5 @@ function registerSnapshot(server, sessions) {
|
|
|
565
573
|
}
|
|
566
574
|
|
|
567
575
|
export { registerDebug, registerDiagnostics, registerDiff, registerListSessions, registerListSteps, registerRun, registerScreenshot, registerSessionClose, registerSessionStart, registerSnapshot };
|
|
568
|
-
//# sourceMappingURL=tools-
|
|
569
|
-
//# sourceMappingURL=tools-
|
|
576
|
+
//# sourceMappingURL=tools-3QWUX4W5.js.map
|
|
577
|
+
//# sourceMappingURL=tools-3QWUX4W5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utility/response.ts","../src/tools/debug.ts","../src/utility/support.ts","../src/utility/diagnostics.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":["resolve","loadConfiguration","z","join"],"mappings":"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","file":"tools-3QWUX4W5.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 { existsSync } 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\nexport type 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\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\nexport async 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\nexport async 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\nexport function 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\nexport async 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\nexport function resolveEffectiveCwd(cwd?: string): string {\n return cwd ?? process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd();\n}\n\nexport function getSupportLoadState(): { loadedProjectRoots: string[]; loadedSupportEntries: string[] } {\n return {\n loadedProjectRoots: [...loadedProjectRoots].sort(),\n loadedSupportEntries: [...loadedSupportEntries].sort(),\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 { loadConfiguration } from '@cucumber/cucumber/api';\nimport { registry } from '@letsrunit/bdd';\nimport { realpathSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { decideHandoff, resolveRuntimeModeOverride } from '../bootstrap';\nimport {\n expandPathPatterns,\n findCucumberConfig,\n getSupportLoadState,\n loadLetsrunitIgnorePatterns,\n resolveEffectiveCwd,\n resolveSupportEntries,\n type SupportEntry,\n} from './support';\n\ndeclare const __LETSRUNIT_VERSION__: string | undefined;\n\nexport type Diagnostics = {\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 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\nexport async function collectDiagnostics(cwd?: string): Promise<Diagnostics> {\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 = [...(useConfiguration.require ?? []), ...(useConfiguration.import ?? [])];\n const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);\n const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);\n const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);\n const supportLoadState = getSupportLoadState();\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 resolveRuntimeModeOverride(),\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 = typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';\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: supportLoadState.loadedProjectRoots,\n loadedSupportEntries: supportLoadState.loadedSupportEntries,\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","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { SessionManager } from '../sessions';\nimport { collectDiagnostics } from '../utility/diagnostics';\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 collectDiagnostics();\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 { McpRuntimeMode } from '../bootstrap';\nimport type { SessionManager } from '../sessions';\nimport { err, text } from '../utility/response';\nimport { loadSupportFiles } from '../utility/support';\n\ninterface Options {\n runtimeMode: McpRuntimeMode;\n}\n\nexport function registerSessionStart(server: McpServer, sessions: SessionManager, opts?: Options): 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\n .string()\n .optional()\n .describe(\n '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 ),\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 (opts?.runtimeMode === '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.5",
|
|
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.5",
|
|
51
|
+
"@letsrunit/controller": "0.14.5",
|
|
52
|
+
"@letsrunit/gherkin": "0.14.5",
|
|
53
|
+
"@letsrunit/journal": "0.14.5",
|
|
54
|
+
"@letsrunit/playwright": "0.14.5",
|
|
55
|
+
"@letsrunit/store": "0.14.5",
|
|
56
|
+
"@letsrunit/utils": "0.14.5",
|
|
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
|
@@ -38,11 +38,28 @@ function sameEntrypoint(a: string | null, b: string | null): boolean {
|
|
|
38
38
|
return a === b;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export function resolveRuntimeModeOverride(): McpRuntimeMode | null {
|
|
42
|
+
const runtimeMode = process.env.LETSRUNIT_MCP_RUNTIME_MODE;
|
|
43
|
+
if (runtimeMode == null || runtimeMode === '') {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
if (runtimeMode === 'project' || runtimeMode === 'standalone') {
|
|
47
|
+
return runtimeMode;
|
|
48
|
+
}
|
|
49
|
+
throw new Error(
|
|
50
|
+
`Invalid LETSRUNIT_MCP_RUNTIME_MODE: ${runtimeMode}. Expected "project" or "standalone".`,
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
41
54
|
export function decideHandoff(
|
|
42
55
|
currentEntrypointPath: string | null,
|
|
43
56
|
projectEntrypointPath: string | null,
|
|
44
|
-
|
|
57
|
+
runtimeModeOverride: McpRuntimeMode | null,
|
|
45
58
|
): HandoffDecision {
|
|
59
|
+
if (runtimeModeOverride) {
|
|
60
|
+
return { shouldHandoff: false, runtimeMode: runtimeModeOverride };
|
|
61
|
+
}
|
|
62
|
+
|
|
46
63
|
if (!projectEntrypointPath) {
|
|
47
64
|
return { shouldHandoff: false, runtimeMode: 'standalone' };
|
|
48
65
|
}
|
|
@@ -51,10 +68,6 @@ export function decideHandoff(
|
|
|
51
68
|
return { shouldHandoff: false, runtimeMode: 'project' };
|
|
52
69
|
}
|
|
53
70
|
|
|
54
|
-
if (isBootstrapped) {
|
|
55
|
-
return { shouldHandoff: false, runtimeMode: 'standalone' };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
71
|
return { shouldHandoff: true, runtimeMode: 'project' };
|
|
59
72
|
}
|
|
60
73
|
|
|
@@ -63,7 +76,6 @@ function runProjectLocalServer(projectEntrypointPath: string): never {
|
|
|
63
76
|
stdio: 'inherit',
|
|
64
77
|
env: {
|
|
65
78
|
...process.env,
|
|
66
|
-
LETSRUNIT_MCP_BOOTSTRAPPED: '1',
|
|
67
79
|
LETSRUNIT_MCP_RUNTIME_MODE: 'project',
|
|
68
80
|
},
|
|
69
81
|
});
|
|
@@ -74,17 +86,16 @@ function runProjectLocalServer(projectEntrypointPath: string): never {
|
|
|
74
86
|
|
|
75
87
|
export function bootstrapProjectServer(): McpRuntimeMode {
|
|
76
88
|
const projectRoot = resolveProjectRoot();
|
|
77
|
-
const
|
|
89
|
+
const runtimeModeOverride = resolveRuntimeModeOverride();
|
|
78
90
|
|
|
79
91
|
const currentEntryPath = toRealpath(fileURLToPath(import.meta.url));
|
|
80
92
|
const projectEntryPath = toRealpath(resolveFromProject('@letsrunit/mcp-server', projectRoot));
|
|
81
93
|
|
|
82
|
-
const decision = decideHandoff(currentEntryPath, projectEntryPath,
|
|
94
|
+
const decision = decideHandoff(currentEntryPath, projectEntryPath, runtimeModeOverride);
|
|
83
95
|
|
|
84
96
|
if (decision.shouldHandoff && projectEntryPath) {
|
|
85
97
|
runProjectLocalServer(projectEntryPath);
|
|
86
98
|
}
|
|
87
99
|
|
|
88
|
-
process.env.LETSRUNIT_MCP_RUNTIME_MODE = decision.runtimeMode;
|
|
89
100
|
return decision.runtimeMode;
|
|
90
101
|
}
|
package/src/index.ts
CHANGED
|
@@ -2,10 +2,10 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
|
2
2
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
3
|
import { bootstrapProjectServer } from './bootstrap';
|
|
4
4
|
|
|
5
|
-
declare const __LETSRUNIT_VERSION__: string;
|
|
5
|
+
declare const __LETSRUNIT_VERSION__: string | undefined;
|
|
6
6
|
|
|
7
7
|
const version = typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';
|
|
8
|
-
bootstrapProjectServer();
|
|
8
|
+
const runtimeMode = bootstrapProjectServer();
|
|
9
9
|
|
|
10
10
|
const { SessionManager } = await import('./sessions');
|
|
11
11
|
const {
|
|
@@ -29,7 +29,7 @@ const server = new McpServer({
|
|
|
29
29
|
websiteUrl: 'https://letsrunit.ai',
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
-
registerSessionStart(server, sessions);
|
|
32
|
+
registerSessionStart(server, sessions, { runtimeMode });
|
|
33
33
|
registerRun(server, sessions);
|
|
34
34
|
registerSnapshot(server, sessions);
|
|
35
35
|
registerScreenshot(server, sessions);
|
package/src/tools/diagnostics.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
3
|
import type { SessionManager } from '../sessions';
|
|
4
|
-
import {
|
|
4
|
+
import { collectDiagnostics } from '../utility/diagnostics';
|
|
5
5
|
import { err, text } from '../utility/response';
|
|
6
6
|
|
|
7
7
|
export function registerDiagnostics(server: McpServer, sessions: SessionManager): void {
|
|
@@ -16,7 +16,7 @@ export function registerDiagnostics(server: McpServer, sessions: SessionManager)
|
|
|
16
16
|
},
|
|
17
17
|
async (input) => {
|
|
18
18
|
try {
|
|
19
|
-
const diagnostics = await
|
|
19
|
+
const diagnostics = await collectDiagnostics();
|
|
20
20
|
const session = sessions.get(input.sessionId);
|
|
21
21
|
const sessionInfo = {
|
|
22
22
|
sessionId: session.id,
|
|
@@ -1,17 +1,27 @@
|
|
|
1
1
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
2
|
import { z } from 'zod';
|
|
3
|
+
import type { McpRuntimeMode } from '../bootstrap';
|
|
3
4
|
import type { SessionManager } from '../sessions';
|
|
4
5
|
import { err, text } from '../utility/response';
|
|
5
6
|
import { loadSupportFiles } from '../utility/support';
|
|
6
7
|
|
|
7
|
-
|
|
8
|
+
interface Options {
|
|
9
|
+
runtimeMode: McpRuntimeMode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function registerSessionStart(server: McpServer, sessions: SessionManager, opts?: Options): void {
|
|
8
13
|
server.registerTool(
|
|
9
14
|
'letsrunit_session_start',
|
|
10
15
|
{
|
|
11
16
|
description:
|
|
12
17
|
'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".',
|
|
13
18
|
inputSchema: {
|
|
14
|
-
baseURL: z
|
|
19
|
+
baseURL: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe(
|
|
23
|
+
'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\\""',
|
|
24
|
+
),
|
|
15
25
|
language: z.string().optional().describe("Browser language code, e.g. 'en', 'fr'"),
|
|
16
26
|
headless: z.boolean().optional().describe('Run browser in headless mode (default: true)'),
|
|
17
27
|
viewportWidth: z.number().int().optional().describe('Viewport width in pixels (default: 1280)'),
|
|
@@ -20,7 +30,7 @@ export function registerSessionStart(server: McpServer, sessions: SessionManager
|
|
|
20
30
|
},
|
|
21
31
|
async (input) => {
|
|
22
32
|
try {
|
|
23
|
-
if (
|
|
33
|
+
if (opts?.runtimeMode === 'project') {
|
|
24
34
|
await loadSupportFiles();
|
|
25
35
|
}
|
|
26
36
|
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
import { loadConfiguration } from '@cucumber/cucumber/api';
|
|
2
|
+
import { registry } from '@letsrunit/bdd';
|
|
3
|
+
import { realpathSync } from 'node:fs';
|
|
4
|
+
import { createRequire } from 'node:module';
|
|
5
|
+
import { resolve } from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { decideHandoff, resolveRuntimeModeOverride } from '../bootstrap';
|
|
8
|
+
import {
|
|
9
|
+
expandPathPatterns,
|
|
10
|
+
findCucumberConfig,
|
|
11
|
+
getSupportLoadState,
|
|
12
|
+
loadLetsrunitIgnorePatterns,
|
|
13
|
+
resolveEffectiveCwd,
|
|
14
|
+
resolveSupportEntries,
|
|
15
|
+
type SupportEntry,
|
|
16
|
+
} from './support';
|
|
17
|
+
|
|
18
|
+
declare const __LETSRUNIT_VERSION__: string | undefined;
|
|
19
|
+
|
|
20
|
+
export type Diagnostics = {
|
|
21
|
+
envProjectCwd: string | null;
|
|
22
|
+
processCwd: string;
|
|
23
|
+
inputCwd: string | null;
|
|
24
|
+
effectiveCwd: string;
|
|
25
|
+
projectRoot: string;
|
|
26
|
+
cucumberConfigPath: string | null;
|
|
27
|
+
supportPatterns: string[];
|
|
28
|
+
ignorePatterns: string[];
|
|
29
|
+
ignoredPaths: string[];
|
|
30
|
+
supportEntries: SupportEntry[];
|
|
31
|
+
loadedProjectRoots: string[];
|
|
32
|
+
loadedSupportEntries: string[];
|
|
33
|
+
mcpServer: {
|
|
34
|
+
version: string;
|
|
35
|
+
executablePath: string | null;
|
|
36
|
+
projectServerUsed: boolean;
|
|
37
|
+
handoffDecision: {
|
|
38
|
+
shouldHandoff: boolean;
|
|
39
|
+
runtimeMode: string;
|
|
40
|
+
};
|
|
41
|
+
serverMcpPath: string | null;
|
|
42
|
+
projectMcpPath: string | null;
|
|
43
|
+
};
|
|
44
|
+
letsrunitEnv: Record<string, string>;
|
|
45
|
+
moduleResolution: {
|
|
46
|
+
serverBddPath: string | null;
|
|
47
|
+
projectBddPath: string | null;
|
|
48
|
+
};
|
|
49
|
+
registry: {
|
|
50
|
+
total: number;
|
|
51
|
+
byType: {
|
|
52
|
+
Given: number;
|
|
53
|
+
When: number;
|
|
54
|
+
Then: number;
|
|
55
|
+
};
|
|
56
|
+
definitions: Array<{
|
|
57
|
+
type: 'Given' | 'When' | 'Then';
|
|
58
|
+
source: string;
|
|
59
|
+
comment?: string;
|
|
60
|
+
}>;
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
function resolveFrom(moduleId: string, fromPath: string): string | null {
|
|
65
|
+
try {
|
|
66
|
+
const req = createRequire(fromPath);
|
|
67
|
+
return req.resolve(moduleId);
|
|
68
|
+
} catch {
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function toRealpath(path: string | null): string | null {
|
|
74
|
+
if (!path) return null;
|
|
75
|
+
try {
|
|
76
|
+
return realpathSync(path);
|
|
77
|
+
} catch {
|
|
78
|
+
return path;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function pickLetsrunitEnv(): Record<string, string> {
|
|
83
|
+
return Object.fromEntries(
|
|
84
|
+
Object.entries(process.env)
|
|
85
|
+
.filter(([key, value]) => key.startsWith('LETSRUNIT_') && typeof value === 'string')
|
|
86
|
+
.map(([key, value]) => [key, value as string]),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function collectDiagnostics(cwd?: string): Promise<Diagnostics> {
|
|
91
|
+
const effectiveCwd = resolveEffectiveCwd(cwd);
|
|
92
|
+
const projectRoot = resolve(effectiveCwd);
|
|
93
|
+
const cucumberConfigPath = findCucumberConfig(projectRoot);
|
|
94
|
+
const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });
|
|
95
|
+
const supportPatterns = [...(useConfiguration.require ?? []), ...(useConfiguration.import ?? [])];
|
|
96
|
+
const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);
|
|
97
|
+
const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);
|
|
98
|
+
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
99
|
+
const supportLoadState = getSupportLoadState();
|
|
100
|
+
const serverBddPath = toRealpath(resolveFrom('@letsrunit/bdd', import.meta.url));
|
|
101
|
+
const projectBddPath = toRealpath(resolveFrom('@letsrunit/bdd', resolve(projectRoot, 'package.json')));
|
|
102
|
+
const projectMcpEntryPath = resolveFrom('@letsrunit/mcp-server', resolve(projectRoot, 'package.json'));
|
|
103
|
+
const currentEntrypointPath = toRealpath(fileURLToPath(import.meta.url));
|
|
104
|
+
const projectEntrypointPath = toRealpath(projectMcpEntryPath);
|
|
105
|
+
const handoffDecision = decideHandoff(
|
|
106
|
+
currentEntrypointPath,
|
|
107
|
+
projectEntrypointPath,
|
|
108
|
+
resolveRuntimeModeOverride(),
|
|
109
|
+
);
|
|
110
|
+
const serverMcpPath = toRealpath(resolveFrom('@letsrunit/mcp-server', import.meta.url));
|
|
111
|
+
const projectMcpPath = toRealpath(projectMcpEntryPath);
|
|
112
|
+
const executablePath = toRealpath(process.argv[1] ?? null);
|
|
113
|
+
const version = typeof __LETSRUNIT_VERSION__ === 'string' ? __LETSRUNIT_VERSION__ : 'unknown';
|
|
114
|
+
const registryDefinitions = registry.defs.map((def) => ({
|
|
115
|
+
type: def.type,
|
|
116
|
+
source: def.source,
|
|
117
|
+
comment: def.comment,
|
|
118
|
+
}));
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
envProjectCwd: process.env.LETSRUNIT_PROJECT_CWD ?? null,
|
|
122
|
+
processCwd: process.cwd(),
|
|
123
|
+
inputCwd: cwd ?? null,
|
|
124
|
+
effectiveCwd,
|
|
125
|
+
projectRoot,
|
|
126
|
+
cucumberConfigPath,
|
|
127
|
+
supportPatterns,
|
|
128
|
+
ignorePatterns,
|
|
129
|
+
ignoredPaths: [...ignoredPaths].sort(),
|
|
130
|
+
supportEntries,
|
|
131
|
+
loadedProjectRoots: supportLoadState.loadedProjectRoots,
|
|
132
|
+
loadedSupportEntries: supportLoadState.loadedSupportEntries,
|
|
133
|
+
mcpServer: {
|
|
134
|
+
version,
|
|
135
|
+
executablePath,
|
|
136
|
+
projectServerUsed: handoffDecision.runtimeMode === 'project',
|
|
137
|
+
handoffDecision: {
|
|
138
|
+
shouldHandoff: handoffDecision.shouldHandoff,
|
|
139
|
+
runtimeMode: handoffDecision.runtimeMode,
|
|
140
|
+
},
|
|
141
|
+
serverMcpPath,
|
|
142
|
+
projectMcpPath,
|
|
143
|
+
},
|
|
144
|
+
letsrunitEnv: pickLetsrunitEnv(),
|
|
145
|
+
moduleResolution: {
|
|
146
|
+
serverBddPath,
|
|
147
|
+
projectBddPath,
|
|
148
|
+
},
|
|
149
|
+
registry: {
|
|
150
|
+
total: registryDefinitions.length,
|
|
151
|
+
byType: {
|
|
152
|
+
Given: registryDefinitions.filter((d) => d.type === 'Given').length,
|
|
153
|
+
When: registryDefinitions.filter((d) => d.type === 'When').length,
|
|
154
|
+
Then: registryDefinitions.filter((d) => d.type === 'Then').length,
|
|
155
|
+
},
|
|
156
|
+
definitions: registryDefinitions,
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
package/src/utility/support.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
1
|
import { loadConfiguration } from '@cucumber/cucumber/api';
|
|
2
|
-
import {
|
|
3
|
-
import { existsSync, realpathSync } from 'node:fs';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
4
3
|
import { glob } from 'node:fs/promises';
|
|
5
|
-
import { createRequire } from 'node:module';
|
|
6
4
|
import { isAbsolute, resolve } from 'node:path';
|
|
7
|
-
import {
|
|
8
|
-
import { decideHandoff } from '../bootstrap';
|
|
9
|
-
|
|
10
|
-
declare const __LETSRUNIT_VERSION__: string;
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
11
6
|
|
|
12
7
|
type CucumberConfig = {
|
|
13
8
|
require?: unknown;
|
|
@@ -17,7 +12,7 @@ type CucumberConfig = {
|
|
|
17
12
|
};
|
|
18
13
|
};
|
|
19
14
|
|
|
20
|
-
type SupportEntry = { kind: 'path'; value: string } | { kind: 'module'; value: string };
|
|
15
|
+
export type SupportEntry = { kind: 'path'; value: string } | { kind: 'module'; value: string };
|
|
21
16
|
|
|
22
17
|
const CUCUMBER_CONFIG_FILES = [
|
|
23
18
|
'cucumber.js',
|
|
@@ -31,50 +26,6 @@ const CUCUMBER_CONFIG_FILES = [
|
|
|
31
26
|
const loadedProjectRoots = new Set<string>();
|
|
32
27
|
const loadedSupportEntries = new Set<string>();
|
|
33
28
|
|
|
34
|
-
export type SupportDiagnostics = {
|
|
35
|
-
envProjectCwd: string | null;
|
|
36
|
-
processCwd: string;
|
|
37
|
-
inputCwd: string | null;
|
|
38
|
-
effectiveCwd: string;
|
|
39
|
-
projectRoot: string;
|
|
40
|
-
cucumberConfigPath: string | null;
|
|
41
|
-
supportPatterns: string[];
|
|
42
|
-
ignorePatterns: string[];
|
|
43
|
-
ignoredPaths: string[];
|
|
44
|
-
supportEntries: SupportEntry[];
|
|
45
|
-
loadedProjectRoots: string[];
|
|
46
|
-
loadedSupportEntries: string[];
|
|
47
|
-
mcpServer: {
|
|
48
|
-
version: string;
|
|
49
|
-
executablePath: string | null;
|
|
50
|
-
projectServerUsed: boolean;
|
|
51
|
-
handoffDecision: {
|
|
52
|
-
shouldHandoff: boolean;
|
|
53
|
-
runtimeMode: string;
|
|
54
|
-
};
|
|
55
|
-
serverMcpPath: string | null;
|
|
56
|
-
projectMcpPath: string | null;
|
|
57
|
-
};
|
|
58
|
-
letsrunitEnv: Record<string, string>;
|
|
59
|
-
moduleResolution: {
|
|
60
|
-
serverBddPath: string | null;
|
|
61
|
-
projectBddPath: string | null;
|
|
62
|
-
};
|
|
63
|
-
registry: {
|
|
64
|
-
total: number;
|
|
65
|
-
byType: {
|
|
66
|
-
Given: number;
|
|
67
|
-
When: number;
|
|
68
|
-
Then: number;
|
|
69
|
-
};
|
|
70
|
-
definitions: Array<{
|
|
71
|
-
type: 'Given' | 'When' | 'Then';
|
|
72
|
-
source: string;
|
|
73
|
-
comment?: string;
|
|
74
|
-
}>;
|
|
75
|
-
};
|
|
76
|
-
};
|
|
77
|
-
|
|
78
29
|
function toStrings(value: unknown): string[] {
|
|
79
30
|
if (!Array.isArray(value)) return [];
|
|
80
31
|
return value.filter((entry): entry is string => typeof entry === 'string');
|
|
@@ -96,7 +47,7 @@ function normalizeMatch(baseDir: string, match: string): string {
|
|
|
96
47
|
return isAbsolute(match) ? resolve(match) : resolve(baseDir, match);
|
|
97
48
|
}
|
|
98
49
|
|
|
99
|
-
async function expandPathPatterns(baseDir: string, patterns: string[]): Promise<Set<string>> {
|
|
50
|
+
export async function expandPathPatterns(baseDir: string, patterns: string[]): Promise<Set<string>> {
|
|
100
51
|
const files = new Set<string>();
|
|
101
52
|
|
|
102
53
|
for (const pattern of patterns) {
|
|
@@ -113,7 +64,7 @@ async function expandPathPatterns(baseDir: string, patterns: string[]): Promise<
|
|
|
113
64
|
return files;
|
|
114
65
|
}
|
|
115
66
|
|
|
116
|
-
async function resolveSupportEntries(baseDir: string, entries: string[]): Promise<SupportEntry[]> {
|
|
67
|
+
export async function resolveSupportEntries(baseDir: string, entries: string[]): Promise<SupportEntry[]> {
|
|
117
68
|
const resolved: SupportEntry[] = [];
|
|
118
69
|
|
|
119
70
|
for (const entry of entries) {
|
|
@@ -135,7 +86,7 @@ async function resolveSupportEntries(baseDir: string, entries: string[]): Promis
|
|
|
135
86
|
return resolved;
|
|
136
87
|
}
|
|
137
88
|
|
|
138
|
-
function findCucumberConfig(cwd: string): string | null {
|
|
89
|
+
export function findCucumberConfig(cwd: string): string | null {
|
|
139
90
|
for (const filename of CUCUMBER_CONFIG_FILES) {
|
|
140
91
|
const path = resolve(cwd, filename);
|
|
141
92
|
if (existsSync(path)) return path;
|
|
@@ -144,7 +95,7 @@ function findCucumberConfig(cwd: string): string | null {
|
|
|
144
95
|
return null;
|
|
145
96
|
}
|
|
146
97
|
|
|
147
|
-
async function loadLetsrunitIgnorePatterns(cwd: string): Promise<string[]> {
|
|
98
|
+
export async function loadLetsrunitIgnorePatterns(cwd: string): Promise<string[]> {
|
|
148
99
|
const configPath = findCucumberConfig(cwd);
|
|
149
100
|
if (!configPath) return [];
|
|
150
101
|
|
|
@@ -154,107 +105,14 @@ async function loadLetsrunitIgnorePatterns(cwd: string): Promise<string[]> {
|
|
|
154
105
|
return toStrings(config.letsrunit?.ignore);
|
|
155
106
|
}
|
|
156
107
|
|
|
157
|
-
function resolveEffectiveCwd(cwd?: string): string {
|
|
108
|
+
export function resolveEffectiveCwd(cwd?: string): string {
|
|
158
109
|
return cwd ?? process.env.LETSRUNIT_PROJECT_CWD ?? process.cwd();
|
|
159
110
|
}
|
|
160
111
|
|
|
161
|
-
function
|
|
162
|
-
try {
|
|
163
|
-
const req = createRequire(fromPath);
|
|
164
|
-
return req.resolve(moduleId);
|
|
165
|
-
} catch {
|
|
166
|
-
return null;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
function toRealpath(path: string | null): string | null {
|
|
171
|
-
if (!path) return null;
|
|
172
|
-
try {
|
|
173
|
-
return realpathSync(path);
|
|
174
|
-
} catch {
|
|
175
|
-
return path;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
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
|
-
|
|
191
|
-
export async function collectSupportDiagnostics(cwd?: string): Promise<SupportDiagnostics> {
|
|
192
|
-
const effectiveCwd = resolveEffectiveCwd(cwd);
|
|
193
|
-
const projectRoot = resolve(effectiveCwd);
|
|
194
|
-
const cucumberConfigPath = findCucumberConfig(projectRoot);
|
|
195
|
-
const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });
|
|
196
|
-
const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];
|
|
197
|
-
const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);
|
|
198
|
-
const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);
|
|
199
|
-
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
200
|
-
const serverBddPath = toRealpath(resolveFrom('@letsrunit/bdd', import.meta.url));
|
|
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
|
-
);
|
|
210
|
-
const serverMcpPath = toRealpath(resolveFrom('@letsrunit/mcp-server', import.meta.url));
|
|
211
|
-
const projectMcpPath = toRealpath(projectMcpEntryPath);
|
|
212
|
-
const executablePath = toRealpath(process.argv[1] ?? null);
|
|
213
|
-
const version = resolveMcpServerVersion();
|
|
214
|
-
const registryDefinitions = registry.defs.map((def) => ({
|
|
215
|
-
type: def.type,
|
|
216
|
-
source: def.source,
|
|
217
|
-
comment: def.comment,
|
|
218
|
-
}));
|
|
219
|
-
|
|
112
|
+
export function getSupportLoadState(): { loadedProjectRoots: string[]; loadedSupportEntries: string[] } {
|
|
220
113
|
return {
|
|
221
|
-
envProjectCwd: process.env.LETSRUNIT_PROJECT_CWD ?? null,
|
|
222
|
-
processCwd: process.cwd(),
|
|
223
|
-
inputCwd: cwd ?? null,
|
|
224
|
-
effectiveCwd,
|
|
225
|
-
projectRoot,
|
|
226
|
-
cucumberConfigPath,
|
|
227
|
-
supportPatterns,
|
|
228
|
-
ignorePatterns,
|
|
229
|
-
ignoredPaths: [...ignoredPaths].sort(),
|
|
230
|
-
supportEntries,
|
|
231
114
|
loadedProjectRoots: [...loadedProjectRoots].sort(),
|
|
232
115
|
loadedSupportEntries: [...loadedSupportEntries].sort(),
|
|
233
|
-
mcpServer: {
|
|
234
|
-
version,
|
|
235
|
-
executablePath,
|
|
236
|
-
projectServerUsed: handoffDecision.runtimeMode === 'project',
|
|
237
|
-
handoffDecision: {
|
|
238
|
-
shouldHandoff: handoffDecision.shouldHandoff,
|
|
239
|
-
runtimeMode: handoffDecision.runtimeMode,
|
|
240
|
-
},
|
|
241
|
-
serverMcpPath,
|
|
242
|
-
projectMcpPath,
|
|
243
|
-
},
|
|
244
|
-
letsrunitEnv: pickLetsrunitEnv(),
|
|
245
|
-
moduleResolution: {
|
|
246
|
-
serverBddPath,
|
|
247
|
-
projectBddPath,
|
|
248
|
-
},
|
|
249
|
-
registry: {
|
|
250
|
-
total: registryDefinitions.length,
|
|
251
|
-
byType: {
|
|
252
|
-
Given: registryDefinitions.filter((d) => d.type === 'Given').length,
|
|
253
|
-
When: registryDefinitions.filter((d) => d.type === 'When').length,
|
|
254
|
-
Then: registryDefinitions.filter((d) => d.type === 'Then').length,
|
|
255
|
-
},
|
|
256
|
-
definitions: registryDefinitions,
|
|
257
|
-
},
|
|
258
116
|
};
|
|
259
117
|
}
|
|
260
118
|
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|
|
@@ -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-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"]}
|