storybook-addon-playwright-mcp 0.0.0

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 ADDED
@@ -0,0 +1,111 @@
1
+ # storybook-addon-playwright-mcp
2
+
3
+ A local **stdio MCP server** that teaches AI coding assistants how to author
4
+ visual/screenshot regression tests for the
5
+ [`storybook-addon-playwright`](../README.md) addon.
6
+
7
+ It is intentionally scoped: the server description tells the assistant to
8
+ consult it **only** when the user asks to _add a story screenshot / visual test_
9
+ or _generate Playwright screenshots_ — not on every Storybook or Playwright task.
10
+
11
+ ## What it knows
12
+
13
+ - The `*.stories.playwright.json` action-file **format and naming convention**
14
+ (same base name, same folder as the story file).
15
+ - How to build **action sets** and the full **action catalog** (searchable).
16
+ - **Selector strategy** — prefer `data-slot` / `data-testid` / `id`.
17
+ - **Screenshot sizing** — prefer focused `takeElementScreenshot` captures, and
18
+ the `options.offset` inset for trimming unwanted edges.
19
+ - How screenshot **images are generated** (addon panel, tRPC endpoint, or the
20
+ visual test suite).
21
+
22
+ ## Tools
23
+
24
+ | Tool | Purpose |
25
+ | -------------------------------- | ------------------------------------------------------------------- |
26
+ | `get_screenshot_authoring_guide` | Workflow + conventions. Start here. Optional `topic` for a section. |
27
+ | `list_playwright_actions` | List actions grouped by category. |
28
+ | `search_playwright_actions` | Rank actions by relevance for a query. |
29
+ | `get_playwright_action` | Full detail + example for one action. |
30
+ | `get_example_playwright_json` | A complete example action file. |
31
+
32
+ ## How it ships
33
+
34
+ This folder is **source only**; it is not published as its own npm package.
35
+ The addon's `tsup` build bundles [`src/cli.ts`](src/cli.ts) into
36
+ `dist/mcp/cli.mjs`, which the root `storybook-addon-playwright` package exposes
37
+ as a second bin:
38
+
39
+ ```jsonc
40
+ // package.json (root)
41
+ "bin": {
42
+ "storybook-addon-playwright": "./dist/cli.js", // the addon's existing CLI (untouched)
43
+ "storybook-addon-playwright-mcp": "./dist/mcp/cli.mjs" // this MCP server
44
+ }
45
+ ```
46
+
47
+ So installing `storybook-addon-playwright` in a project makes the
48
+ `storybook-addon-playwright-mcp` bin available — no separate install, published
49
+ by the same release pipeline.
50
+
51
+ ## Usage (in a consuming project)
52
+
53
+ Once `storybook-addon-playwright` is installed, register the server with your MCP
54
+ client **in that project**. `npx` resolves the bin from the project's
55
+ `node_modules/.bin` — there is no `storybook-addon-playwright-mcp` package on
56
+ npm, so it must resolve locally:
57
+
58
+ ```jsonc
59
+ {
60
+ "mcpServers": {
61
+ "storybook-playwright-screenshots": {
62
+ "command": "npx",
63
+ "args": ["storybook-addon-playwright-mcp"],
64
+ },
65
+ },
66
+ }
67
+ ```
68
+
69
+ For Claude Code (project scope, so the server starts in the project root):
70
+
71
+ ```bash
72
+ claude mcp add -s project storybook-playwright-screenshots -- npx storybook-addon-playwright-mcp
73
+ ```
74
+
75
+ ### Troubleshooting
76
+
77
+ `connection closed: initialize response` at startup means the bin was not
78
+ resolved locally and `npx` fell back to the registry, where the name 404s.
79
+ Causes and fixes:
80
+
81
+ - **Registered in user/global scope** (or the client starts servers outside the
82
+ project) → re-register in project scope, or use an absolute path:
83
+ `node /abs/path/to/project/node_modules/storybook-addon-playwright/dist/mcp/cli.mjs`.
84
+ - **Working inside this repo** (a package does not link its own bins under
85
+ pnpm) → point at the build output: `node <repo>/dist/mcp/cli.mjs`
86
+ (run `pnpm build` first).
87
+ - **Really need it standalone** → name the owning package:
88
+ `npx -y -p storybook-addon-playwright storybook-addon-playwright-mcp`. Never
89
+ `npx -y storybook-addon-playwright-mcp` — that resolves to a nonexistent
90
+ package.
91
+
92
+ ## Development
93
+
94
+ This folder's dependencies (`@modelcontextprotocol/sdk`, `zod`, `vitest`, `tsx`,
95
+ `typescript`) are all present at the repo root, so **no separate install is
96
+ needed** — run the checks from the repo root:
97
+
98
+ ```bash
99
+ pnpm exec tsc --noEmit -p mcp/tsconfig.json # typecheck
100
+ pnpm exec vitest run --config mcp/vitest.config.ts # tests (incl. schema-sync)
101
+ pnpm exec tsx mcp/src/cli.ts # run the server over stdio
102
+ ```
103
+
104
+ CI runs the typecheck + tests via the "MCP server checks" step. The
105
+ [`schema-sync`](test/schema-sync.test.ts) test fails the build if
106
+ [`src/data/actions.ts`](src/data/actions.ts) drifts from the addon's generated
107
+ action schema (`src/api/server/data/action-schema.json`, produced from the
108
+ `PlaywrightPage` interface).
109
+
110
+ The production bin is produced by the root build (`pnpm build` at the repo
111
+ root → `dist/mcp/cli.mjs`).
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "storybook-addon-playwright-mcp",
3
+ "version": "0.0.0",
4
+ "type": "module",
5
+ "description": "Source for the MCP server shipped as the `storybook-addon-playwright-mcp` bin of the storybook-addon-playwright package. Not published on its own — the addon's tsup build bundles mcp/src/cli.ts into dist/mcp/cli.mjs.",
6
+ "license": "MIT",
7
+ "scripts": {
8
+ "dev": "tsx src/cli.ts",
9
+ "typecheck": "tsc --noEmit",
10
+ "test": "vitest --run --passWithNoTests",
11
+ "test:watch": "vitest --watch"
12
+ },
13
+ "dependencies": {
14
+ "@modelcontextprotocol/sdk": "^1.29.0",
15
+ "zod": "^4.4.3"
16
+ },
17
+ "devDependencies": {
18
+ "@types/node": "^25.7.0",
19
+ "tsx": "^4.21.0",
20
+ "typescript": "~5.9.3",
21
+ "vitest": "^4.1.6"
22
+ }
23
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from 'node:fs';
3
+ import { startServer } from './server.js';
4
+
5
+ function readVersion(): string {
6
+ // Resolves to the storybook-addon-playwright package.json in both layouts:
7
+ // dev : mcp/src/cli.ts -> ../../package.json (repo root)
8
+ // built: dist/mcp/cli.mjs -> ../../package.json (installed package root)
9
+ try {
10
+ const pkgUrl = new URL('../../package.json', import.meta.url);
11
+ const pkg = JSON.parse(readFileSync(pkgUrl, 'utf8')) as { version?: string };
12
+ return pkg.version ?? '0.0.0';
13
+ } catch {
14
+ return '0.0.0';
15
+ }
16
+ }
17
+
18
+ await startServer(readVersion());
package/src/content.ts ADDED
@@ -0,0 +1,84 @@
1
+ import type { ActionRegistry, Guide, PlaywrightAction } from './types.js';
2
+ import { actions } from './data/actions.js';
3
+ import { exampleExplanation, exampleFile, exampleFileName } from './data/example.js';
4
+ import { guides } from './data/guides.js';
5
+
6
+ const JSON_INDENT = 2;
7
+
8
+ /** Action catalog keyed by action name. */
9
+ export const actionRegistry: ActionRegistry = Object.fromEntries(
10
+ actions.map((action) => [action.name, action]),
11
+ );
12
+
13
+ /** Every guide keyed by its topic id. */
14
+ const guideById: Record<string, Guide> = Object.fromEntries(
15
+ guides.map((guide) => [guide.id, guide]),
16
+ );
17
+
18
+ /** Valid `topic` values for the authoring-guide tool. */
19
+ export const guideTopics: readonly string[] = guides.map((guide) => guide.id);
20
+
21
+ /** Look up a single action by its exact name. */
22
+ export function getAction(name: string): PlaywrightAction | undefined {
23
+ return Object.hasOwn(actionRegistry, name) ? actionRegistry[name] : undefined;
24
+ }
25
+
26
+ /** Group action names by category for the list tool. */
27
+ export function listActionsByCategory(): Record<string, string[]> {
28
+ const grouped: Record<string, string[]> = {};
29
+
30
+ for (const action of actions) {
31
+ (grouped[action.category] ??= []).push(action.name);
32
+ }
33
+
34
+ for (const names of Object.values(grouped)) {
35
+ names.sort((a, b) => a.localeCompare(b));
36
+ }
37
+
38
+ return grouped;
39
+ }
40
+
41
+ const guideIndex = guides.map((guide) => `- \`${guide.id}\` — ${guide.title}`).join('\n');
42
+
43
+ /**
44
+ * Build the markdown response for the authoring-guide tool.
45
+ *
46
+ * - no topic → overview + workflow + an index of the remaining topics;
47
+ * - `all` → every guide concatenated;
48
+ * - a topic → that single guide;
49
+ * - unknown → an error listing the valid topics.
50
+ */
51
+ export function getGuideText(topic?: string): { text: string; isError?: boolean } {
52
+ if (!topic) {
53
+ const overview = guideById.overview?.body ?? '';
54
+ const workflow = guideById.workflow?.body ?? '';
55
+
56
+ return {
57
+ text: `${overview}\n\n---\n\n${workflow}\n\n---\n\n## More topics\n\nCall this tool again with a \`topic\` for detail:\n\n${guideIndex}`,
58
+ };
59
+ }
60
+
61
+ if (topic === 'all') {
62
+ return {
63
+ text: guides.map((guide) => guide.body).join('\n\n---\n\n'),
64
+ };
65
+ }
66
+
67
+ const guide = guideById[topic];
68
+
69
+ if (!guide) {
70
+ return {
71
+ isError: true,
72
+ text: `Unknown topic "${topic}". Valid topics: ${guideTopics.join(', ')}, all.`,
73
+ };
74
+ }
75
+
76
+ return { text: guide.body };
77
+ }
78
+
79
+ /** Build the markdown response for the example tool. */
80
+ export function getExampleText(): string {
81
+ const json = JSON.stringify(exampleFile, null, JSON_INDENT);
82
+
83
+ return `# Example: \`${exampleFileName}\`\n\n\`\`\`json\n${json}\n\`\`\`\n\n${exampleExplanation}`;
84
+ }