@letsrunit/mcp-server 0.8.0 → 0.9.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 CHANGED
@@ -56,6 +56,7 @@ args = ["-y", "@letsrunit/mcp-server@latest"]
56
56
  |------|-------------|
57
57
  | `letsrunit_session_start` | Launch a new browser session. Set `baseURL` to enable relative paths like `Given I'm on the homepage`. |
58
58
  | `letsrunit_run` | Execute Gherkin steps or a complete feature. Accepts a single step, multiple steps, a Scenario, or a full Feature. |
59
+ | `letsrunit_list_steps` | List available step definitions for a session. Optionally filter by `Given`, `When`, or `Then`. |
59
60
  | `letsrunit_snapshot` | Get the current page HTML, scrubbed for LLM consumption. Scope to a DOM subtree with `selector`. |
60
61
  | `letsrunit_screenshot` | Take a screenshot. Optionally crop to a selector or highlight elements before capturing. |
61
62
  | `letsrunit_debug` | Evaluate JavaScript on the current page via `page.evaluate()`. Use for debugging, not test logic. |
package/dist/index.js CHANGED
@@ -174,6 +174,29 @@ function registerDiff(server2, sessions2) {
174
174
  }
175
175
  );
176
176
  }
177
+ var stepTypeSchema = z.enum(["Given", "When", "Then"]);
178
+ function registerListSteps(server2, sessions2) {
179
+ server2.registerTool(
180
+ "letsrunit_list_steps",
181
+ {
182
+ description: "List available step definitions for a session. Optionally filter by step type (Given/When/Then).",
183
+ inputSchema: {
184
+ sessionId: z.string().describe("Session ID returned by letsrunit_session_start"),
185
+ type: stepTypeSchema.optional().describe("Optional step type filter")
186
+ }
187
+ },
188
+ async (input) => {
189
+ try {
190
+ const session = sessions2.get(input.sessionId);
191
+ sessions2.touch(input.sessionId);
192
+ const steps = session.controller.listSteps(input.type);
193
+ return text(JSON.stringify({ steps }));
194
+ } catch (e) {
195
+ return err(`List steps failed: ${e.message}`);
196
+ }
197
+ }
198
+ );
199
+ }
177
200
 
178
201
  // src/tools/list-sessions.ts
179
202
  function registerListSessions(server2, sessions2) {
@@ -334,6 +357,11 @@ function registerSessionStart(server2, sessions2) {
334
357
  }
335
358
  );
336
359
  }
360
+ var stripAttributesSchema = z.nativeEnum({
361
+ none: 0,
362
+ semantic: 1,
363
+ aggressive: 2
364
+ });
337
365
  function registerSnapshot(server2, sessions2) {
338
366
  server2.registerTool(
339
367
  "letsrunit_snapshot",
@@ -345,7 +373,7 @@ function registerSnapshot(server2, sessions2) {
345
373
  dropHidden: z.boolean().optional().describe("Remove hidden/inert nodes (default: true)"),
346
374
  dropHead: z.boolean().optional().describe("Remove the <head> element (default: true)"),
347
375
  pickMain: z.boolean().optional().describe("Keep only the <main> element (default: auto)"),
348
- stripAttributes: z.union([z.literal(0), z.literal(1), z.literal(2)]).optional().describe("Attribute allowlist level: 0=none, 1=semantic (default), 2=aggressive")
376
+ stripAttributes: stripAttributesSchema.optional().describe("Attribute allowlist level: 0=none, 1=semantic (default), 2=aggressive")
349
377
  }
350
378
  },
351
379
  async (input) => {
@@ -389,6 +417,7 @@ registerSnapshot(server, sessions);
389
417
  registerScreenshot(server, sessions);
390
418
  registerDebug(server, sessions);
391
419
  registerSessionClose(server, sessions);
420
+ registerListSteps(server, sessions);
392
421
  registerListSessions(server, sessions);
393
422
  registerDiff(server, sessions);
394
423
  var transport = new StdioServerTransport();
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sessions.ts","../src/utility/response.ts","../src/tools/debug.ts","../src/tools/diff.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","../src/index.ts"],"names":["server","sessions","join","z"],"mappings":"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","file":"index.js","sourcesContent":["import { Controller, type ControllerOptions } from '@letsrunit/controller';\nimport { Journal, MemorySink } from '@letsrunit/journal';\nimport { join } from 'node:path';\n\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\nexport interface Session {\n id: string;\n controller: Controller;\n sink: MemorySink;\n journal: Journal<MemorySink>;\n artifactDir: string;\n createdAt: number;\n lastActivity: number;\n stepCount: number;\n}\n\nconst BASE_ARTIFACT_DIR = process.env.LETSRUNIT_ARTIFACT_DIR ?? join(process.env.HOME ?? '/tmp', '.letsrunit', 'artifacts');\n\nfunction sessionId(): string {\n return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);\n}\n\nexport class SessionManager {\n private readonly sessions = new Map<string, Session>();\n private readonly timers = new Map<string, ReturnType<typeof setTimeout>>();\n\n async create(options: ControllerOptions = {}): Promise<Session> {\n const id = sessionId();\n const artifactDir = join(BASE_ARTIFACT_DIR, id);\n\n const sink = new MemorySink(artifactDir);\n const journal = new Journal(sink);\n\n const controller = await Controller.launch({ ...options, journal });\n\n const session: Session = {\n id,\n controller,\n sink,\n journal,\n artifactDir,\n createdAt: Date.now(),\n lastActivity: Date.now(),\n stepCount: 0,\n };\n\n this.sessions.set(id, session);\n this.resetTimer(id);\n\n return session;\n }\n\n get(id: string): Session {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`Session not found: ${id}`);\n return session;\n }\n\n has(id: string): boolean {\n return this.sessions.has(id);\n }\n\n touch(id: string): void {\n const session = this.sessions.get(id);\n if (session) {\n session.lastActivity = Date.now();\n this.resetTimer(id);\n }\n }\n\n list(): Session[] {\n return Array.from(this.sessions.values());\n }\n\n async close(id: string): Promise<void> {\n const session = this.sessions.get(id);\n if (!session) return;\n\n clearTimeout(this.timers.get(id));\n this.timers.delete(id);\n this.sessions.delete(id);\n\n await session.controller.close();\n }\n\n private resetTimer(id: string): void {\n clearTimeout(this.timers.get(id));\n const timer = setTimeout(() => this.close(id), SESSION_TIMEOUT_MS);\n // Allow Node.js to exit even if a timer is pending\n if (typeof timer === 'object' && 'unref' in timer) timer.unref();\n this.timers.set(id, timer);\n }\n}\n","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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { unifiedHtmlDiff } from '@letsrunit/playwright';\nimport { openStore, findLastRun, 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 run 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 runs 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 runs 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 run = findLastRun(db, input.scenarioId, 'passed', allowedCommits ?? undefined);\n if (!run) {\n return err(\n allowedCommits\n ? 'No passing run found for this scenario in the current git ancestry. Try gitTreeOnly: false or run cucumber first.'\n : 'No passing run found for this scenario.',\n );\n }\n\n const artifacts = findArtifacts(db, run.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 run. 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.filename.endsWith('.png'))\n .map((a) => join(artifactDir, a.filename));\n\n return text(\n JSON.stringify({\n diff,\n baseline: {\n runId: run.id,\n commit: run.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 { 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 { parseFeature } from '@letsrunit/gherkin';\nimport { computeStepId, computeScenarioId } from '@letsrunit/store';\nimport type { SessionManager } from '../sessions';\nimport { normalizeGherkin } from '../utility/gherkin';\nimport { err, text } from '../utility/response';\n\nfunction scenarioIdFromGherkin(gherkin: string): string {\n const { steps } = parseFeature(gherkin);\n const stepIds = steps.map((s) => computeStepId(s));\n return computeScenarioId(stepIds);\n}\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';\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 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\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: z\n .union([z.literal(0), z.literal(1), z.literal(2)])\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","import { createRequire } from 'module';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { SessionManager } from './sessions';\n\nconst { version } = createRequire(import.meta.url)('../package.json') as { version: string };\nimport {\n registerDebug,\n registerDiff,\n registerListSessions,\n registerRun,\n registerScreenshot,\n registerSessionClose,\n registerSessionStart,\n registerSnapshot,\n} from './tools';\n\nconst sessions = new SessionManager();\n\nconst server = new McpServer({\n name: 'letsrunit',\n version,\n websiteUrl: 'https://letsrunit.ai',\n});\n\nregisterSessionStart(server, sessions);\nregisterRun(server, sessions);\nregisterSnapshot(server, sessions);\nregisterScreenshot(server, sessions);\nregisterDebug(server, sessions);\nregisterSessionClose(server, sessions);\nregisterListSessions(server, sessions);\nregisterDiff(server, sessions);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"]}
1
+ {"version":3,"sources":["../src/sessions.ts","../src/utility/response.ts","../src/tools/debug.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","../src/index.ts"],"names":["server","sessions","join","z"],"mappings":"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","file":"index.js","sourcesContent":["import { Controller, type ControllerOptions } from '@letsrunit/controller';\nimport { Journal, MemorySink } from '@letsrunit/journal';\nimport { join } from 'node:path';\n\nconst SESSION_TIMEOUT_MS = 30 * 60 * 1000; // 30 minutes\n\nexport interface Session {\n id: string;\n controller: Controller;\n sink: MemorySink;\n journal: Journal<MemorySink>;\n artifactDir: string;\n createdAt: number;\n lastActivity: number;\n stepCount: number;\n}\n\nconst BASE_ARTIFACT_DIR = process.env.LETSRUNIT_ARTIFACT_DIR ?? join(process.env.HOME ?? '/tmp', '.letsrunit', 'artifacts');\n\nfunction sessionId(): string {\n return Math.random().toString(36).slice(2, 10) + Date.now().toString(36);\n}\n\nexport class SessionManager {\n private readonly sessions = new Map<string, Session>();\n private readonly timers = new Map<string, ReturnType<typeof setTimeout>>();\n\n async create(options: ControllerOptions = {}): Promise<Session> {\n const id = sessionId();\n const artifactDir = join(BASE_ARTIFACT_DIR, id);\n\n const sink = new MemorySink(artifactDir);\n const journal = new Journal(sink);\n\n const controller = await Controller.launch({ ...options, journal });\n\n const session: Session = {\n id,\n controller,\n sink,\n journal,\n artifactDir,\n createdAt: Date.now(),\n lastActivity: Date.now(),\n stepCount: 0,\n };\n\n this.sessions.set(id, session);\n this.resetTimer(id);\n\n return session;\n }\n\n get(id: string): Session {\n const session = this.sessions.get(id);\n if (!session) throw new Error(`Session not found: ${id}`);\n return session;\n }\n\n has(id: string): boolean {\n return this.sessions.has(id);\n }\n\n touch(id: string): void {\n const session = this.sessions.get(id);\n if (session) {\n session.lastActivity = Date.now();\n this.resetTimer(id);\n }\n }\n\n list(): Session[] {\n return Array.from(this.sessions.values());\n }\n\n async close(id: string): Promise<void> {\n const session = this.sessions.get(id);\n if (!session) return;\n\n clearTimeout(this.timers.get(id));\n this.timers.delete(id);\n this.sessions.delete(id);\n\n await session.controller.close();\n }\n\n private resetTimer(id: string): void {\n clearTimeout(this.timers.get(id));\n const timer = setTimeout(() => this.close(id), SESSION_TIMEOUT_MS);\n // Allow Node.js to exit even if a timer is pending\n if (typeof timer === 'object' && 'unref' in timer) timer.unref();\n this.timers.set(id, timer);\n }\n}\n","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 { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { unifiedHtmlDiff } from '@letsrunit/playwright';\nimport { openStore, findLastRun, 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 run 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 runs 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 runs 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 run = findLastRun(db, input.scenarioId, 'passed', allowedCommits ?? undefined);\n if (!run) {\n return err(\n allowedCommits\n ? 'No passing run found for this scenario in the current git ancestry. Try gitTreeOnly: false or run cucumber first.'\n : 'No passing run found for this scenario.',\n );\n }\n\n const artifacts = findArtifacts(db, run.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 run. 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.filename.endsWith('.png'))\n .map((a) => join(artifactDir, a.filename));\n\n return text(\n JSON.stringify({\n diff,\n baseline: {\n runId: run.id,\n commit: run.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 { parseFeature } from '@letsrunit/gherkin';\nimport { computeStepId, computeScenarioId } from '@letsrunit/store';\nimport type { SessionManager } from '../sessions';\nimport { normalizeGherkin } from '../utility/gherkin';\nimport { err, text } from '../utility/response';\n\nfunction scenarioIdFromGherkin(gherkin: string): string {\n const { steps } = parseFeature(gherkin);\n const stepIds = steps.map((s) => computeStepId(s));\n return computeScenarioId(stepIds);\n}\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';\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 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","import { createRequire } from 'module';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { SessionManager } from './sessions';\n\nconst { version } = createRequire(import.meta.url)('../package.json') as { version: string };\nimport {\n registerDebug,\n registerDiff,\n registerListSteps,\n registerListSessions,\n registerRun,\n registerScreenshot,\n registerSessionClose,\n registerSessionStart,\n registerSnapshot,\n} from './tools';\n\nconst sessions = new SessionManager();\n\nconst server = new McpServer({\n name: 'letsrunit',\n version,\n websiteUrl: 'https://letsrunit.ai',\n});\n\nregisterSessionStart(server, sessions);\nregisterRun(server, sessions);\nregisterSnapshot(server, sessions);\nregisterScreenshot(server, sessions);\nregisterDebug(server, sessions);\nregisterSessionClose(server, sessions);\nregisterListSteps(server, sessions);\nregisterListSessions(server, sessions);\nregisterDiff(server, sessions);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letsrunit/mcp-server",
3
- "version": "0.8.0",
3
+ "version": "0.9.1",
4
4
  "description": "MCP server for letsrunit — AI-agent browser test generation and execution",
5
5
  "keywords": [
6
6
  "testing",
@@ -46,12 +46,12 @@
46
46
  },
47
47
  "packageManager": "yarn@4.10.3",
48
48
  "dependencies": {
49
- "@letsrunit/controller": "0.8.0",
50
- "@letsrunit/gherkin": "0.8.0",
51
- "@letsrunit/journal": "0.8.0",
52
- "@letsrunit/playwright": "0.8.0",
53
- "@letsrunit/store": "0.8.0",
54
- "@letsrunit/utils": "0.8.0",
49
+ "@letsrunit/controller": "0.9.1",
50
+ "@letsrunit/gherkin": "0.9.1",
51
+ "@letsrunit/journal": "0.9.1",
52
+ "@letsrunit/playwright": "0.9.1",
53
+ "@letsrunit/store": "0.9.1",
54
+ "@letsrunit/utils": "0.9.1",
55
55
  "@modelcontextprotocol/sdk": "^1.26.0",
56
56
  "@playwright/test": "^1.57.0",
57
57
  "zod": "^4.3.5"
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ const { version } = createRequire(import.meta.url)('../package.json') as { versi
7
7
  import {
8
8
  registerDebug,
9
9
  registerDiff,
10
+ registerListSteps,
10
11
  registerListSessions,
11
12
  registerRun,
12
13
  registerScreenshot,
@@ -29,6 +30,7 @@ registerSnapshot(server, sessions);
29
30
  registerScreenshot(server, sessions);
30
31
  registerDebug(server, sessions);
31
32
  registerSessionClose(server, sessions);
33
+ registerListSteps(server, sessions);
32
34
  registerListSessions(server, sessions);
33
35
  registerDiff(server, sessions);
34
36
 
@@ -1,5 +1,6 @@
1
1
  export { registerDebug } from './debug';
2
2
  export { registerDiff } from './diff';
3
+ export { registerListSteps } from './list-steps';
3
4
  export { registerListSessions } from './list-sessions';
4
5
  export { registerRun } from './run';
5
6
  export { registerScreenshot } from './screenshot';
@@ -0,0 +1,31 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import { z } from 'zod';
3
+ import { type SessionManager } from '../sessions';
4
+ import { err, text } from '../utility/response';
5
+
6
+ const stepTypeSchema = z.enum(['Given', 'When', 'Then']);
7
+
8
+ export function registerListSteps(server: McpServer, sessions: SessionManager): void {
9
+ server.registerTool(
10
+ 'letsrunit_list_steps',
11
+ {
12
+ description:
13
+ 'List available step definitions for a session. Optionally filter by step type (Given/When/Then).',
14
+ inputSchema: {
15
+ sessionId: z.string().describe('Session ID returned by letsrunit_session_start'),
16
+ type: stepTypeSchema.optional().describe('Optional step type filter'),
17
+ },
18
+ },
19
+ async (input) => {
20
+ try {
21
+ const session = sessions.get(input.sessionId);
22
+ sessions.touch(input.sessionId);
23
+
24
+ const steps = session.controller.listSteps(input.type);
25
+ return text(JSON.stringify({ steps }));
26
+ } catch (e) {
27
+ return err(`List steps failed: ${(e as Error).message}`);
28
+ }
29
+ },
30
+ );
31
+ }
@@ -4,6 +4,12 @@ import { z } from 'zod';
4
4
  import { type SessionManager } from '../sessions';
5
5
  import { err, text } from '../utility/response';
6
6
 
7
+ const stripAttributesSchema = z.nativeEnum({
8
+ none: 0,
9
+ semantic: 1,
10
+ aggressive: 2,
11
+ });
12
+
7
13
  export function registerSnapshot(server: McpServer, sessions: SessionManager): void {
8
14
  server.registerTool(
9
15
  'letsrunit_snapshot',
@@ -19,8 +25,7 @@ export function registerSnapshot(server: McpServer, sessions: SessionManager): v
19
25
  dropHidden: z.boolean().optional().describe('Remove hidden/inert nodes (default: true)'),
20
26
  dropHead: z.boolean().optional().describe('Remove the <head> element (default: true)'),
21
27
  pickMain: z.boolean().optional().describe('Keep only the <main> element (default: auto)'),
22
- stripAttributes: z
23
- .union([z.literal(0), z.literal(1), z.literal(2)])
28
+ stripAttributes: stripAttributesSchema
24
29
  .optional()
25
30
  .describe('Attribute allowlist level: 0=none, 1=semantic (default), 2=aggressive'),
26
31
  },