@letsrunit/mcp-server 0.14.5 → 0.15.1
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/README.md +3 -0
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/{tools-3QWUX4W5.js → tools-4463TLYZ.js} +70 -9
- package/dist/tools-4463TLYZ.js.map +1 -0
- package/package.json +8 -8
- package/src/index.ts +2 -0
- package/src/tools/index.ts +1 -0
- package/src/tools/reload.ts +56 -0
- package/src/utility/support.ts +40 -5
- package/dist/tools-3QWUX4W5.js.map +0 -1
package/README.md
CHANGED
|
@@ -61,6 +61,8 @@ At runtime, letsrunit checks the current project:
|
|
|
61
61
|
|
|
62
62
|
So custom steps from `features/support/**` are available when `@letsrunit/mcp-server` is installed in that project.
|
|
63
63
|
|
|
64
|
+
When you edit existing support files during a long-running agent session, call `letsrunit_reload` to rebuild and reload step definitions without restarting the agent.
|
|
65
|
+
|
|
64
66
|
## Tools
|
|
65
67
|
|
|
66
68
|
| Tool | Description |
|
|
@@ -71,6 +73,7 @@ So custom steps from `features/support/**` are available when `@letsrunit/mcp-se
|
|
|
71
73
|
| `letsrunit_snapshot` | Get the current page HTML, scrubbed for LLM consumption. Scope to a DOM subtree with `selector`. |
|
|
72
74
|
| `letsrunit_screenshot` | Take a screenshot. Optionally crop to a selector or highlight elements before capturing. |
|
|
73
75
|
| `letsrunit_debug` | Evaluate JavaScript on the current page via `page.evaluate()`. Use for debugging, not test logic. |
|
|
76
|
+
| `letsrunit_reload` | Rebuild and reload built-in + project support step definitions after step-file changes. Available in project runtime mode. |
|
|
74
77
|
| `letsrunit_diagnostics` | Return runtime diagnostics (`cwd`, `LETSRUNIT_PROJECT_CWD`, detected cucumber config, resolved support entries). Available only when `LETSRUNIT_MCP_DIAGNOSTICS=enabled`. |
|
|
75
78
|
| `letsrunit_session_close` | Close a browser session and release its resources. |
|
|
76
79
|
| `letsrunit_list_sessions` | List all active browser sessions. |
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ 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.
|
|
8
|
+
var version = "0.15.1" ;
|
|
9
9
|
var runtimeMode = bootstrapProjectServer();
|
|
10
10
|
var { SessionManager } = await import('./sessions-BYH3NJQG.js');
|
|
11
11
|
var {
|
|
@@ -14,12 +14,13 @@ var {
|
|
|
14
14
|
registerDiff,
|
|
15
15
|
registerListSteps,
|
|
16
16
|
registerListSessions,
|
|
17
|
+
registerReload,
|
|
17
18
|
registerRun,
|
|
18
19
|
registerScreenshot,
|
|
19
20
|
registerSessionClose,
|
|
20
21
|
registerSessionStart,
|
|
21
22
|
registerSnapshot
|
|
22
|
-
} = await import('./tools-
|
|
23
|
+
} = await import('./tools-4463TLYZ.js');
|
|
23
24
|
var sessions = new SessionManager();
|
|
24
25
|
var server = new McpServer({
|
|
25
26
|
name: "letsrunit",
|
|
@@ -34,6 +35,7 @@ registerDebug(server, sessions);
|
|
|
34
35
|
registerSessionClose(server, sessions);
|
|
35
36
|
registerListSteps(server, sessions);
|
|
36
37
|
registerListSessions(server, sessions);
|
|
38
|
+
registerReload(server, { runtimeMode });
|
|
37
39
|
registerDiff(server, sessions);
|
|
38
40
|
if (process.env.LETSRUNIT_MCP_DIAGNOSTICS === "enabled") {
|
|
39
41
|
registerDiagnostics(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,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
|
+
{"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,cAAA;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,cAAA,CAAe,MAAA,EAAQ,EAAE,WAAA,EAAa,CAAA;AACtC,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 registerReload,\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);\nregisterReload(server, { runtimeMode });\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"]}
|
|
@@ -126,32 +126,53 @@ function getSupportLoadState() {
|
|
|
126
126
|
loadedSupportEntries: [...loadedSupportEntries].sort()
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
|
-
|
|
129
|
+
function clearSupportLoadState() {
|
|
130
|
+
loadedProjectRoots.clear();
|
|
131
|
+
loadedSupportEntries.clear();
|
|
132
|
+
}
|
|
133
|
+
function buildReloadedFileUrl(path) {
|
|
134
|
+
const url = pathToFileURL(path);
|
|
135
|
+
url.searchParams.set("letsrunitReload", Date.now().toString(36));
|
|
136
|
+
return url.href;
|
|
137
|
+
}
|
|
138
|
+
async function loadSupportFiles(cwd, options) {
|
|
130
139
|
const projectRoot = resolve(resolveEffectiveCwd(cwd));
|
|
131
|
-
|
|
140
|
+
const forceReload = options?.forceReload === true;
|
|
141
|
+
if (!forceReload && loadedProjectRoots.has(projectRoot)) {
|
|
142
|
+
return { projectRoot, supportEntriesLoaded: 0, ignoredEntries: 0 };
|
|
143
|
+
}
|
|
132
144
|
const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });
|
|
133
145
|
const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];
|
|
134
146
|
if (supportPatterns.length === 0) {
|
|
135
147
|
loadedProjectRoots.add(projectRoot);
|
|
136
|
-
return;
|
|
148
|
+
return { projectRoot, supportEntriesLoaded: 0, ignoredEntries: 0 };
|
|
137
149
|
}
|
|
138
150
|
const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);
|
|
139
151
|
const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);
|
|
140
152
|
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
153
|
+
let supportEntriesLoaded = 0;
|
|
154
|
+
let ignoredEntries = 0;
|
|
141
155
|
for (const entry of supportEntries) {
|
|
142
156
|
if (entry.kind === "path" && ignoredPaths.has(entry.value)) {
|
|
157
|
+
ignoredEntries += 1;
|
|
143
158
|
continue;
|
|
144
159
|
}
|
|
145
160
|
const key = `${entry.kind}:${entry.value}`;
|
|
146
|
-
if (loadedSupportEntries.has(key)) continue;
|
|
161
|
+
if (!forceReload && loadedSupportEntries.has(key)) continue;
|
|
147
162
|
if (entry.kind === "path") {
|
|
148
|
-
await import(pathToFileURL(entry.value).href);
|
|
163
|
+
await (forceReload ? import(buildReloadedFileUrl(entry.value)) : import(pathToFileURL(entry.value).href));
|
|
149
164
|
} else {
|
|
150
165
|
await import(entry.value);
|
|
151
166
|
}
|
|
167
|
+
supportEntriesLoaded += 1;
|
|
152
168
|
loadedSupportEntries.add(key);
|
|
153
169
|
}
|
|
154
170
|
loadedProjectRoots.add(projectRoot);
|
|
171
|
+
return { projectRoot, supportEntriesLoaded, ignoredEntries };
|
|
172
|
+
}
|
|
173
|
+
async function reloadSupportFiles(cwd) {
|
|
174
|
+
clearSupportLoadState();
|
|
175
|
+
return loadSupportFiles(cwd, { forceReload: true });
|
|
155
176
|
}
|
|
156
177
|
|
|
157
178
|
// src/utility/diagnostics.ts
|
|
@@ -199,7 +220,7 @@ async function collectDiagnostics(cwd) {
|
|
|
199
220
|
const serverMcpPath = toRealpath(resolveFrom("@letsrunit/mcp-server", import.meta.url));
|
|
200
221
|
const projectMcpPath = toRealpath(projectMcpEntryPath);
|
|
201
222
|
const executablePath = toRealpath(process.argv[1] ?? null);
|
|
202
|
-
const version = "0.
|
|
223
|
+
const version = "0.15.1" ;
|
|
203
224
|
const registryDefinitions = registry.defs.map((def) => ({
|
|
204
225
|
type: def.type,
|
|
205
226
|
source: def.source,
|
|
@@ -436,6 +457,46 @@ function registerRun(server, sessions) {
|
|
|
436
457
|
}
|
|
437
458
|
);
|
|
438
459
|
}
|
|
460
|
+
async function resetBuiltInStepRegistry() {
|
|
461
|
+
const bdd = await import('@letsrunit/bdd');
|
|
462
|
+
const reset = bdd.resetRegistryToBuiltInSteps;
|
|
463
|
+
if (typeof reset !== "function") {
|
|
464
|
+
throw new Error(
|
|
465
|
+
"Installed @letsrunit/bdd does not expose resetRegistryToBuiltInSteps. Update @letsrunit/bdd to a compatible version."
|
|
466
|
+
);
|
|
467
|
+
}
|
|
468
|
+
reset();
|
|
469
|
+
}
|
|
470
|
+
function registerReload(server, options) {
|
|
471
|
+
server.registerTool(
|
|
472
|
+
"letsrunit_reload",
|
|
473
|
+
{
|
|
474
|
+
description: "Reload built-in and project support step definitions without restarting the MCP server.",
|
|
475
|
+
inputSchema: {
|
|
476
|
+
cwd: z.string().optional().describe("Project directory to resolve cucumber support files from. Defaults to current project cwd.")
|
|
477
|
+
}
|
|
478
|
+
},
|
|
479
|
+
async (input) => {
|
|
480
|
+
if (options.runtimeMode !== "project") {
|
|
481
|
+
return err("Reload failed: letsrunit_reload is only available in project runtime mode.");
|
|
482
|
+
}
|
|
483
|
+
try {
|
|
484
|
+
await resetBuiltInStepRegistry();
|
|
485
|
+
const result = await reloadSupportFiles(input.cwd);
|
|
486
|
+
return text(
|
|
487
|
+
JSON.stringify({
|
|
488
|
+
reloaded: true,
|
|
489
|
+
projectRoot: result.projectRoot,
|
|
490
|
+
supportEntriesLoaded: result.supportEntriesLoaded,
|
|
491
|
+
ignoredEntries: result.ignoredEntries
|
|
492
|
+
})
|
|
493
|
+
);
|
|
494
|
+
} catch (e) {
|
|
495
|
+
return err(`Reload failed: ${e.message}`);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
);
|
|
499
|
+
}
|
|
439
500
|
function registerScreenshot(server, sessions) {
|
|
440
501
|
server.registerTool(
|
|
441
502
|
"letsrunit_screenshot",
|
|
@@ -572,6 +633,6 @@ function registerSnapshot(server, sessions) {
|
|
|
572
633
|
);
|
|
573
634
|
}
|
|
574
635
|
|
|
575
|
-
export { registerDebug, registerDiagnostics, registerDiff, registerListSessions, registerListSteps, registerRun, registerScreenshot, registerSessionClose, registerSessionStart, registerSnapshot };
|
|
576
|
-
//# sourceMappingURL=tools-
|
|
577
|
-
//# sourceMappingURL=tools-
|
|
636
|
+
export { registerDebug, registerDiagnostics, registerDiff, registerListSessions, registerListSteps, registerReload, registerRun, registerScreenshot, registerSessionClose, registerSessionStart, registerSnapshot };
|
|
637
|
+
//# sourceMappingURL=tools-4463TLYZ.js.map
|
|
638
|
+
//# sourceMappingURL=tools-4463TLYZ.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/reload.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-4463TLYZ.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\nexport type SupportLoadResult = {\n projectRoot: string;\n supportEntriesLoaded: number;\n ignoredEntries: number;\n};\n\ntype LoadSupportOptions = {\n forceReload?: boolean;\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\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 function clearSupportLoadState(): void {\n loadedProjectRoots.clear();\n loadedSupportEntries.clear();\n}\n\nfunction buildReloadedFileUrl(path: string): string {\n const url = pathToFileURL(path);\n url.searchParams.set('letsrunitReload', Date.now().toString(36));\n return url.href;\n}\n\nexport async function loadSupportFiles(cwd?: string, options?: LoadSupportOptions): Promise<SupportLoadResult> {\n const projectRoot = resolve(resolveEffectiveCwd(cwd));\n const forceReload = options?.forceReload === true;\n\n if (!forceReload && loadedProjectRoots.has(projectRoot)) {\n return { projectRoot, supportEntriesLoaded: 0, ignoredEntries: 0 };\n }\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 { projectRoot, supportEntriesLoaded: 0, ignoredEntries: 0 };\n }\n\n const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);\n const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);\n const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);\n let supportEntriesLoaded = 0;\n let ignoredEntries = 0;\n\n for (const entry of supportEntries) {\n if (entry.kind === 'path' && ignoredPaths.has(entry.value)) {\n ignoredEntries += 1;\n continue;\n }\n\n const key = `${entry.kind}:${entry.value}`;\n if (!forceReload && loadedSupportEntries.has(key)) continue;\n\n if (entry.kind === 'path') {\n await import(forceReload ? buildReloadedFileUrl(entry.value) : pathToFileURL(entry.value).href);\n } else {\n await import(entry.value);\n }\n\n supportEntriesLoaded += 1;\n loadedSupportEntries.add(key);\n }\n\n loadedProjectRoots.add(projectRoot);\n return { projectRoot, supportEntriesLoaded, ignoredEntries };\n}\n\nexport async function reloadSupportFiles(cwd?: string): Promise<SupportLoadResult> {\n clearSupportLoadState();\n return loadSupportFiles(cwd, { forceReload: true });\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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\nimport type { McpRuntimeMode } from '../bootstrap';\nimport { err, text } from '../utility/response';\nimport { reloadSupportFiles } from '../utility/support';\n\ntype Options = {\n runtimeMode: McpRuntimeMode;\n};\n\nasync function resetBuiltInStepRegistry(): Promise<void> {\n const bdd = (await import('@letsrunit/bdd')) as Record<string, unknown>;\n const reset = bdd.resetRegistryToBuiltInSteps;\n if (typeof reset !== 'function') {\n throw new Error(\n 'Installed @letsrunit/bdd does not expose resetRegistryToBuiltInSteps. Update @letsrunit/bdd to a compatible version.',\n );\n }\n reset();\n}\n\nexport function registerReload(server: McpServer, options: Options): void {\n server.registerTool(\n 'letsrunit_reload',\n {\n description:\n 'Reload built-in and project support step definitions without restarting the MCP server.',\n inputSchema: {\n cwd: z\n .string()\n .optional()\n .describe('Project directory to resolve cucumber support files from. Defaults to current project cwd.'),\n },\n },\n async (input) => {\n if (options.runtimeMode !== 'project') {\n return err('Reload failed: letsrunit_reload is only available in project runtime mode.');\n }\n\n try {\n await resetBuiltInStepRegistry();\n const result = await reloadSupportFiles(input.cwd);\n return text(\n JSON.stringify({\n reloaded: true,\n projectRoot: result.projectRoot,\n supportEntriesLoaded: result.supportEntriesLoaded,\n ignoredEntries: result.ignoredEntries,\n }),\n );\n } catch (e) {\n return err(`Reload 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.
|
|
3
|
+
"version": "0.15.1",
|
|
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.
|
|
51
|
-
"@letsrunit/controller": "0.
|
|
52
|
-
"@letsrunit/gherkin": "0.
|
|
53
|
-
"@letsrunit/journal": "0.
|
|
54
|
-
"@letsrunit/playwright": "0.
|
|
55
|
-
"@letsrunit/store": "0.
|
|
56
|
-
"@letsrunit/utils": "0.
|
|
50
|
+
"@letsrunit/bdd": "0.15.1",
|
|
51
|
+
"@letsrunit/controller": "0.15.1",
|
|
52
|
+
"@letsrunit/gherkin": "0.15.1",
|
|
53
|
+
"@letsrunit/journal": "0.15.1",
|
|
54
|
+
"@letsrunit/playwright": "0.15.1",
|
|
55
|
+
"@letsrunit/store": "0.15.1",
|
|
56
|
+
"@letsrunit/utils": "0.15.1",
|
|
57
57
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
58
58
|
"@playwright/test": "^1.57.0",
|
|
59
59
|
"zod": "^4.3.5"
|
package/src/index.ts
CHANGED
|
@@ -14,6 +14,7 @@ const {
|
|
|
14
14
|
registerDiff,
|
|
15
15
|
registerListSteps,
|
|
16
16
|
registerListSessions,
|
|
17
|
+
registerReload,
|
|
17
18
|
registerRun,
|
|
18
19
|
registerScreenshot,
|
|
19
20
|
registerSessionClose,
|
|
@@ -37,6 +38,7 @@ registerDebug(server, sessions);
|
|
|
37
38
|
registerSessionClose(server, sessions);
|
|
38
39
|
registerListSteps(server, sessions);
|
|
39
40
|
registerListSessions(server, sessions);
|
|
41
|
+
registerReload(server, { runtimeMode });
|
|
40
42
|
registerDiff(server, sessions);
|
|
41
43
|
if (process.env.LETSRUNIT_MCP_DIAGNOSTICS === 'enabled') {
|
|
42
44
|
registerDiagnostics(server, sessions);
|
package/src/tools/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export { registerDiff } from './diff';
|
|
|
4
4
|
export { registerListSteps } from './list-steps';
|
|
5
5
|
export { registerListSessions } from './list-sessions';
|
|
6
6
|
export { registerRun } from './run';
|
|
7
|
+
export { registerReload } from './reload';
|
|
7
8
|
export { registerScreenshot } from './screenshot';
|
|
8
9
|
export { registerSessionClose } from './session-close';
|
|
9
10
|
export { registerSessionStart } from './session-start';
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import type { McpRuntimeMode } from '../bootstrap';
|
|
4
|
+
import { err, text } from '../utility/response';
|
|
5
|
+
import { reloadSupportFiles } from '../utility/support';
|
|
6
|
+
|
|
7
|
+
type Options = {
|
|
8
|
+
runtimeMode: McpRuntimeMode;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
async function resetBuiltInStepRegistry(): Promise<void> {
|
|
12
|
+
const bdd = (await import('@letsrunit/bdd')) as Record<string, unknown>;
|
|
13
|
+
const reset = bdd.resetRegistryToBuiltInSteps;
|
|
14
|
+
if (typeof reset !== 'function') {
|
|
15
|
+
throw new Error(
|
|
16
|
+
'Installed @letsrunit/bdd does not expose resetRegistryToBuiltInSteps. Update @letsrunit/bdd to a compatible version.',
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
reset();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function registerReload(server: McpServer, options: Options): void {
|
|
23
|
+
server.registerTool(
|
|
24
|
+
'letsrunit_reload',
|
|
25
|
+
{
|
|
26
|
+
description:
|
|
27
|
+
'Reload built-in and project support step definitions without restarting the MCP server.',
|
|
28
|
+
inputSchema: {
|
|
29
|
+
cwd: z
|
|
30
|
+
.string()
|
|
31
|
+
.optional()
|
|
32
|
+
.describe('Project directory to resolve cucumber support files from. Defaults to current project cwd.'),
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
async (input) => {
|
|
36
|
+
if (options.runtimeMode !== 'project') {
|
|
37
|
+
return err('Reload failed: letsrunit_reload is only available in project runtime mode.');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
try {
|
|
41
|
+
await resetBuiltInStepRegistry();
|
|
42
|
+
const result = await reloadSupportFiles(input.cwd);
|
|
43
|
+
return text(
|
|
44
|
+
JSON.stringify({
|
|
45
|
+
reloaded: true,
|
|
46
|
+
projectRoot: result.projectRoot,
|
|
47
|
+
supportEntriesLoaded: result.supportEntriesLoaded,
|
|
48
|
+
ignoredEntries: result.ignoredEntries,
|
|
49
|
+
}),
|
|
50
|
+
);
|
|
51
|
+
} catch (e) {
|
|
52
|
+
return err(`Reload failed: ${(e as Error).message}`);
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
);
|
|
56
|
+
}
|
package/src/utility/support.ts
CHANGED
|
@@ -26,6 +26,16 @@ const CUCUMBER_CONFIG_FILES = [
|
|
|
26
26
|
const loadedProjectRoots = new Set<string>();
|
|
27
27
|
const loadedSupportEntries = new Set<string>();
|
|
28
28
|
|
|
29
|
+
export type SupportLoadResult = {
|
|
30
|
+
projectRoot: string;
|
|
31
|
+
supportEntriesLoaded: number;
|
|
32
|
+
ignoredEntries: number;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type LoadSupportOptions = {
|
|
36
|
+
forceReload?: boolean;
|
|
37
|
+
};
|
|
38
|
+
|
|
29
39
|
function toStrings(value: unknown): string[] {
|
|
30
40
|
if (!Array.isArray(value)) return [];
|
|
31
41
|
return value.filter((entry): entry is string => typeof entry === 'string');
|
|
@@ -116,37 +126,62 @@ export function getSupportLoadState(): { loadedProjectRoots: string[]; loadedSup
|
|
|
116
126
|
};
|
|
117
127
|
}
|
|
118
128
|
|
|
119
|
-
export
|
|
129
|
+
export function clearSupportLoadState(): void {
|
|
130
|
+
loadedProjectRoots.clear();
|
|
131
|
+
loadedSupportEntries.clear();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function buildReloadedFileUrl(path: string): string {
|
|
135
|
+
const url = pathToFileURL(path);
|
|
136
|
+
url.searchParams.set('letsrunitReload', Date.now().toString(36));
|
|
137
|
+
return url.href;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
export async function loadSupportFiles(cwd?: string, options?: LoadSupportOptions): Promise<SupportLoadResult> {
|
|
120
141
|
const projectRoot = resolve(resolveEffectiveCwd(cwd));
|
|
121
|
-
|
|
142
|
+
const forceReload = options?.forceReload === true;
|
|
143
|
+
|
|
144
|
+
if (!forceReload && loadedProjectRoots.has(projectRoot)) {
|
|
145
|
+
return { projectRoot, supportEntriesLoaded: 0, ignoredEntries: 0 };
|
|
146
|
+
}
|
|
122
147
|
|
|
123
148
|
const { useConfiguration } = await loadConfiguration({}, { cwd: projectRoot });
|
|
124
149
|
const supportPatterns = [...toStrings(useConfiguration.require), ...toStrings(useConfiguration.import)];
|
|
125
150
|
if (supportPatterns.length === 0) {
|
|
126
151
|
loadedProjectRoots.add(projectRoot);
|
|
127
|
-
return;
|
|
152
|
+
return { projectRoot, supportEntriesLoaded: 0, ignoredEntries: 0 };
|
|
128
153
|
}
|
|
129
154
|
|
|
130
155
|
const ignorePatterns = await loadLetsrunitIgnorePatterns(projectRoot);
|
|
131
156
|
const ignoredPaths = await expandPathPatterns(projectRoot, ignorePatterns);
|
|
132
157
|
const supportEntries = await resolveSupportEntries(projectRoot, supportPatterns);
|
|
158
|
+
let supportEntriesLoaded = 0;
|
|
159
|
+
let ignoredEntries = 0;
|
|
133
160
|
|
|
134
161
|
for (const entry of supportEntries) {
|
|
135
162
|
if (entry.kind === 'path' && ignoredPaths.has(entry.value)) {
|
|
163
|
+
ignoredEntries += 1;
|
|
136
164
|
continue;
|
|
137
165
|
}
|
|
138
166
|
|
|
139
167
|
const key = `${entry.kind}:${entry.value}`;
|
|
140
|
-
if (loadedSupportEntries.has(key)) continue;
|
|
168
|
+
if (!forceReload && loadedSupportEntries.has(key)) continue;
|
|
141
169
|
|
|
142
170
|
if (entry.kind === 'path') {
|
|
143
|
-
await import(pathToFileURL(entry.value).href);
|
|
171
|
+
await import(forceReload ? buildReloadedFileUrl(entry.value) : pathToFileURL(entry.value).href);
|
|
144
172
|
} else {
|
|
145
173
|
await import(entry.value);
|
|
146
174
|
}
|
|
147
175
|
|
|
176
|
+
supportEntriesLoaded += 1;
|
|
148
177
|
loadedSupportEntries.add(key);
|
|
149
178
|
}
|
|
150
179
|
|
|
151
180
|
loadedProjectRoots.add(projectRoot);
|
|
181
|
+
return { projectRoot, supportEntriesLoaded, ignoredEntries };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export async function reloadSupportFiles(cwd?: string): Promise<SupportLoadResult> {
|
|
185
|
+
clearSupportLoadState();
|
|
186
|
+
return loadSupportFiles(cwd, { forceReload: true });
|
|
152
187
|
}
|
|
@@ -1 +0,0 @@
|
|
|
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"]}
|